Index: /OFFICIAL/FCM_V1.5/lib/Fcm/Build/Fortran.pm
===================================================================
--- /OFFICIAL/FCM_V1.5/lib/Fcm/Build/Fortran.pm	(revision 12)
+++ /OFFICIAL/FCM_V1.5/lib/Fcm/Build/Fortran.pm	(revision 12)
@@ -0,0 +1,536 @@
+# ------------------------------------------------------------------------------
+# (C) Crown copyright Met Office. All rights reserved.
+# For further details please refer to the file COPYRIGHT.txt
+# which you should have received as part of this distribution.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+
+# ------------------------------------------------------------------------------
+package Fcm::Build::Fortran;
+
+use Text::Balanced qw{extract_bracketed extract_delimited};
+
+# Actions of this class
+my %ACTION_OF = (extract_interface => \&_extract_interface);
+
+# Regular expressions
+# Matches a variable attribute
+my $RE_ATTR = qr{
+    allocatable|dimension|external|intent|optional|parameter|pointer|save|target
+}imsx;
+# Matches a name
+my $RE_NAME = qr{[A-Za-z]\w*}imsx;
+# Matches a specification type
+my $RE_SPEC = qr{
+    character|complex|double\s*precision|integer|logical|real|type
+}imsx;
+# Matches the identifier of a program unit that does not have arguments
+my $RE_UNIT_BASE = qr{block\s*data|module|program}imsx;
+# Matches the identifier of a program unit that has arguments
+my $RE_UNIT_CALL = qr{function|subroutine}imsx;
+# Matches the identifier of any program unit
+my $RE_UNIT      = qr{$RE_UNIT_BASE|$RE_UNIT_CALL}msx;
+my %RE = (
+    # A comment line
+    COMMENT     => qr{\A\s*(?:!|\z)}msx,
+    # A trailing comment, capture the expression before the comment
+    COMMENT_END => qr{\A([^'"]*?)\s*!.*\z}msx,
+    # A contination marker, capture the expression before the marker
+    CONT        => qr{\A(.*)&\s*\z}msx,
+    # A contination marker at the beginning of a line, capture the marker and
+    # the expression after the marker
+    CONT_LEAD   => qr{\A(\s*&)(.*)\z}msx,
+    # Capture a variable identifier, removing any type component expression
+    NAME_COMP   => qr{\b($RE_NAME)(?:\s*\%\s*$RE_NAME)*\b}msx,
+    # Matches the first identifier in a line
+    NAME_LEAD   => qr{\A\s*$RE_NAME\s*}msx,
+    # Captures a name identifier after a comma, and the expression after
+    NAME_LIST   => qr{\A(?:.*?)\s*,\s*($RE_NAME)\b(.*)\z}msx,
+    # Captures the next quote character
+    QUOTE       => qr{\A[^'"]*(['"])}msx,
+    # Matches an attribute declaration
+    TYPE_ATTR   => qr{\A\s*($RE_ATTR)\b}msx,
+    # Matches a type declaration
+    TYPE_SPEC   => qr{\A\s*($RE_SPEC)\b}msx,
+    # Captures the expression after one or more program unit attributes
+    UNIT_ATTR   => qr{\A\s*(?:(?:elemental|recursive|pure)\s+)+(.*)\z}imsx,
+    # Captures the identifier and the symbol of a program unit with no arguments
+    UNIT_BASE   => qr{\A\s*($RE_UNIT_BASE)\s+($RE_NAME)\s*\z}imsx,
+    # Captures the identifier and the symbol of a program unit with arguments
+    UNIT_CALL   => qr{\A\s*($RE_UNIT_CALL)\s+($RE_NAME)\b}imsx,
+    # Captures the end of a program unit, its identifier and its symbol
+    UNIT_END    => qr{\A\s*(end)(?:\s+($RE_NAME)(?:\s+($RE_NAME))?)?\s*\z}imsx,
+    # Captures the expression after a program unit type specification
+    UNIT_SPEC   => qr{\A\s*$RE_SPEC\b(.*)\z}imsx,
+);
+
+# Keywords in type declaration statements
+my %TYPE_DECL_KEYWORD_SET = map { ($_, 1) } qw{
+    allocatable
+    dimension
+    in
+    inout
+    intent
+    kind
+    len
+    optional
+    out
+    parameter
+    pointer
+    save
+    target
+};
+
+# Creates and returns an instance of this class.
+sub new {
+    my ($class) = @_;
+    bless(
+        sub {
+            my $key = shift();
+            if (!exists($ACTION_OF{$key})) {
+                return;
+            }
+            $ACTION_OF{$key}->(@_);
+        },
+        $class,
+    );
+}
+
+# Methods.
+for my $key (keys(%ACTION_OF)) {
+    no strict qw{refs};
+    *{$key} = sub { my $self = shift(); $self->($key, @_) };
+}
+
+# Extracts the calling interfaces of top level subroutines and functions from
+# the $handle for reading Fortran sources.
+sub _extract_interface {
+    my ($handle) = @_;
+    map { _present_line($_) } @{_reduce_to_interface(_load($handle))};
+}
+
+# Reads $handle for the next Fortran statement, handling continuations.
+sub _load {
+    my ($handle) = @_;
+    my $ctx = {signature_token_set_of => {}, statements => []};
+    my $state = {
+        in_contains  => undef, # in a "contains" section of a program unit
+        in_interface => undef, # in an "interface" block
+        in_quote     => undef, # in a multi-line quote
+        stack        => [],    # program unit stack
+    };
+    my $NEW_STATEMENT = sub {
+        {   name        => q{}, # statement name, e.g. function, integer, ...
+            lines       => [],  # original lines in the statement
+            line_number => 0,   # line number (start) in the original source
+            symbol      => q{}, # name of a program unit (signature, end)
+            type        => q{}, # e.g. signature, use, type, attr, end
+            value       => q{}, # the actual value of the statement
+        };
+    };
+    my $statement;
+LINE:
+    while (my $line = readline($handle)) {
+        if (!defined($statement)) {
+            $statement = $NEW_STATEMENT->();
+        }
+        my $value = $line;
+        chomp($value);
+        # Pre-processor directives and continuation
+        if (!$statement->{line_number} && index($value, '#') == 0) {
+            $statement->{line_number} = $.;
+            $statement->{name}        = 'cpp';
+        }
+        if ($statement->{name} eq 'cpp') {
+            push(@{$statement->{lines}}, $line);
+            $statement->{value} .= $value;
+            if (rindex($value, '\\') != length($value) - 1) {
+                $statement = undef;
+            }
+            next LINE;
+        }
+        # Normal Fortran
+        if ($value =~ $RE{COMMENT}) {
+            next LINE;
+        }
+        if (!$statement->{line_number}) {
+            $statement->{line_number} = $.;
+        }
+        my ($cont_head, $cont_tail);
+        if ($statement->{line_number} != $.) { # is a continuation
+            ($cont_head, $cont_tail) = $value =~ $RE{CONT_LEAD};
+            if ($cont_head) {
+                $value = $cont_tail;
+            }
+        }
+        # Correctly handle ! and & in quotes
+        my ($head, $tail) = (q{}, $value);
+        if ($state->{in_quote} && index($value, $state->{in_quote}) >= 0) {
+            my $index = index($value, $state->{in_quote});
+            $head = substr($value, 0, $index + 1);
+            $tail
+                = length($value) > $index + 1
+                ? substr($value, $index + 2)
+                : q{};
+            $state->{in_quote} = undef;
+        }
+        if (!$state->{in_quote}) {
+            while ($tail) {
+                if (index($tail, q{!}) >= 0) {
+                    if (!($tail =~ s/$RE{COMMENT_END}/$1/)) {
+                        ($head, $tail, $state->{in_quote})
+                            = _load_extract_quote($head, $tail);
+                    }
+                }
+                else {
+                    while (index($tail, q{'}) > 0
+                        || index($tail, q{"}) > 0)
+                    {
+                        ($head, $tail, $state->{in_quote})
+                            = _load_extract_quote($head, $tail);
+                    }
+                    $head .= $tail;
+                    $tail = q{};
+                }
+            }
+        }
+        $cont_head ||= q{};
+        push(@{$statement->{lines}}, $cont_head . $head . $tail . "\n");
+        $statement->{value} .= $head . $tail;
+        # Process a statement only if it is marked with a continuation
+        if (!($statement->{value} =~ s/$RE{CONT}/$1/)) {
+            $statement->{value} =~ s{\s+\z}{}msx;
+            if (_process($statement, $ctx, $state)) {
+                push(@{$ctx->{statements}}, $statement);
+            }
+            $statement = undef;
+        }
+    }
+    return $ctx;
+}
+
+# Helper, removes a quoted string from $tail.
+sub _load_extract_quote {
+    my ($head, $tail) = @_;
+    my ($extracted, $remainder, $prefix)
+        = extract_delimited($tail, q{'"}, qr{[^'"]*}msx, q{});
+    if ($extracted) {
+        return ($head . $prefix . $extracted, $remainder);
+    }
+    else {
+        my ($quote) = $tail =~ $RE{QUOTE};
+        return ($head . $tail, q{}, $quote);
+    }
+}
+
+# Study statements and put attributes into array $statements
+sub _process {
+    my ($statement, $ctx, $state) = @_;
+    my $name;
+
+    # End Interface
+    if ($state->{in_interface}) {
+        if ($statement->{value} =~ qr{\A\s*end\s*interface\b}imsx) {
+            $state->{in_interface} = 0;
+        }
+        return;
+    }
+
+    # End Program Unit
+    if (@{$state->{stack}} && $statement->{value} =~ qr{\A\s*end\b}imsx) {
+        my ($end, $type, $symbol) = lc($statement->{value}) =~ $RE{UNIT_END};
+        if (!$end) {
+            return;
+        }
+        my ($top_type, $top_symbol) = @{$state->{stack}->[-1]};
+        if (!$type
+            || $top_type eq $type && (!$symbol || $top_symbol eq $symbol))
+        {
+            pop(@{$state->{stack}});
+            if ($state->{in_contains} && !@{$state->{stack}}) {
+                $state->{in_contains} = 0;
+            }
+            if (!$state->{in_contains}) {
+                $statement->{name}   = $top_type;
+                $statement->{symbol} = $top_symbol;
+                $statement->{type}   = 'end';
+                return $statement;
+            }
+        }
+        return;
+    }
+
+    # Interface/Contains
+    ($name) = $statement->{value} =~ qr{\A\s*(contains|interface)\b}imsx;
+    if ($name) {
+        $state->{'in_' . lc($name)} = 1;
+        return;
+    }
+
+    # Program Unit
+    my ($type, $symbol, @tokens) = _process_prog_unit($statement->{value});
+    if ($type) {
+        push(@{$state->{stack}}, [$type, $symbol]);
+        if ($state->{in_contains}) {
+            return;
+        }
+        $statement->{name}   = lc($type);
+        $statement->{type}   = 'signature';
+        $statement->{symbol} = lc($symbol);
+        $ctx->{signature_token_set_of}{$symbol}
+            = {map { (lc($_) => 1) } @tokens};
+        return $statement;
+    }
+    if ($state->{in_contains}) {
+        return;
+    }
+
+    # Use
+    if ($statement->{value} =~ qr{\A\s*(use)\b}imsx) {
+        $statement->{name} = 'use';
+        $statement->{type} = 'use';
+        return $statement;
+    }
+
+    # Type Declarations
+    ($name) = $statement->{value} =~ $RE{TYPE_SPEC};
+    if ($name) {
+        $name =~ s{\s}{}gmsx;
+        $statement->{name} = lc($name);
+        $statement->{type} = 'type';
+        return $statement;
+    }
+
+    # Attribute Statements
+    ($name) = $statement->{value} =~ $RE{TYPE_ATTR};
+    if ($name) {
+        $statement->{name} = $name;
+        $statement->{type} = 'attr';
+        return $statement;
+    }
+}
+
+# Parse a statement for program unit header. Returns a list containing the type,
+# the symbol and the signature tokens of the program unit.
+sub _process_prog_unit {
+    my ($string) = @_;
+    my ($type, $symbol, @args) = (q{}, q{});
+    # Is it a blockdata, module or program?
+    ($type, $symbol) = $string =~ $RE{UNIT_BASE};
+    if ($type) {
+        $type = lc($type);
+        $type =~ s{\s*}{}gmsx;
+        return ($type, $symbol);
+    }
+    # Remove the attribute and type declaration of a procedure
+    $string =~ s/$RE{UNIT_ATTR}/$1/;
+    my ($match) = $string =~ $RE{UNIT_SPEC};
+    if ($match) {
+        $string = $match;
+        extract_bracketed($string);
+    }
+    # Is it a function or subroutine?
+    ($type, $symbol) = lc($string) =~ $RE{UNIT_CALL};
+    if (!$type) {
+        return;
+    }
+    my $extracted = extract_bracketed($string, q{()}, qr{[^(]*}msx);
+
+    # Get signature tokens from SUBROUTINE/FUNCTION
+    if ($extracted) {
+        $extracted =~ s{\s}{}gmsx;
+        @args = split(q{,}, substr($extracted, 1, length($extracted) - 2));
+        if ($type eq 'function') {
+            my $result = extract_bracketed($string, q{()}, qr{[^(]*}msx);
+            if ($result) {
+                $result =~ s{\A\(\s*(.*?)\s*\)\z}{$1}msx; # remove braces
+                push(@args, $result);
+            }
+            else {
+                push(@args, $symbol);
+            }
+        }
+    }
+    return (lc($type), lc($symbol), map { lc($_) } @args);
+}
+
+# Reduces the list of statements to contain only the interface block.
+sub _reduce_to_interface {
+    my ($ctx) = @_;
+    my (%token_set, @interface_statements);
+STATEMENT:
+    for my $statement (reverse(@{$ctx->{statements}})) {
+        if ($statement->{type} eq 'end'
+            && grep { $_ eq $statement->{name} } qw{subroutine function})
+        {
+            push(@interface_statements, $statement);
+            %token_set
+                = %{$ctx->{signature_token_set_of}{$statement->{symbol}}};
+            next STATEMENT;
+        }
+        if ($statement->{type} eq 'signature'
+            && grep { $_ eq $statement->{name} } qw{subroutine function})
+        {
+            push(@interface_statements, $statement);
+            %token_set = ();
+            next STATEMENT;
+        }
+        if ($statement->{type} eq 'use') {
+            my ($head, $tail)
+                = split(qr{\s*:\s*}msx, lc($statement->{value}), 2);
+            if ($tail) {
+                my @imports = map { [split(qr{\s*=>\s*}msx, $_, 2)] }
+                    split(qr{\s*,\s*}msx, $tail);
+                my @useful_imports
+                    = grep { exists($token_set{$_->[0]}) } @imports;
+                if (!@useful_imports) {
+                    next STATEMENT;
+                }
+                if (@imports != @useful_imports) {
+                    my @token_strings
+                        = map { $_->[0] . ($_->[1] ? ' => ' . $_->[1] : q{}) }
+                        @useful_imports;
+                    my ($last, @rest) = reverse(@token_strings);
+                    my @token_lines
+                        = (reverse(map { $_ . q{,&} } @rest), $last);
+                    push(
+                        @interface_statements,
+                        {   lines => [
+                                sprintf("%s:&\n", $head),
+                                (map { sprintf(" & %s\n", $_) } @token_lines),
+                            ]
+                        },
+                    );
+                    next STATEMENT;
+                }
+            }
+            push(@interface_statements, $statement);
+            next STATEMENT;
+        }
+        if ($statement->{type} eq 'attr') {
+            my ($spec, @tokens) = ($statement->{value} =~ /$RE{NAME_COMP}/g);
+            if (grep { exists($token_set{$_}) } @tokens) {
+                for my $token (@tokens) {
+                    $token_set{$token} = 1;
+                }
+                push(@interface_statements, $statement);
+                next STATEMENT;
+            }
+        }
+        if ($statement->{type} eq 'type') {
+            my ($variable_string, $spec_string)
+                = reverse(split('::', lc($statement->{value}), 2));
+            if ($spec_string) {
+                $spec_string =~ s{$RE{NAME_LEAD}}{}msx;
+            }
+            else {
+                # The first expression in the statement is the type + attrib
+                $variable_string =~ s{$RE{NAME_LEAD}}{}msx;
+                $spec_string = extract_bracketed($variable_string, '()',
+                    qr{[\s\*]*}msx);
+            }
+            # Useful tokens are those that comes after a comma
+            my $tail = q{,} . lc($variable_string);
+            my @tokens;
+            while ($tail) {
+                if ($tail =~ qr{\A\s*['"]}msx) {
+                    extract_delimited($tail, q{'"}, qr{\A[^'"]*}msx, q{});
+                }
+                elsif ($tail =~ qr{\A\s*\(}msx) {
+                    extract_bracketed($tail, '()', qr{\A[^(]*}msx);
+                }
+                else {
+                    my $token;
+                    ($token, $tail) = $tail =~ $RE{NAME_LIST};
+                    if ($token && $token_set{$token}) {
+                        @tokens = ($variable_string =~ /$RE{NAME_COMP}/g);
+                        $tail = q{};
+                    }
+                }
+            }
+            if (@tokens && $spec_string) {
+                my @spec_tokens = (lc($spec_string) =~ /$RE{NAME_COMP}/g);
+                push(
+                    @tokens,
+                    (   grep { !exists($TYPE_DECL_KEYWORD_SET{$_}) }
+                            @spec_tokens
+                    ),
+                );
+            }
+            if (grep { exists($token_set{$_}) } @tokens) {
+                for my $token (@tokens) {
+                    $token_set{$token} = 1;
+                }
+                push(@interface_statements, $statement);
+                next STATEMENT;
+            }
+        }
+    }
+    if (!@interface_statements) {
+        return [];
+    }
+    [   {lines => ["interface\n"]},
+        reverse(@interface_statements),
+        {lines => ["end interface\n"]},
+    ];
+}
+
+# Processes and returns the line of the statement.
+sub _present_line {
+    my ($statement) = @_;
+    map {
+        s{\s+}{ }gmsx;      # collapse multiple spaces
+        s{\s+\z}{\n}msx;    # remove trailing spaces
+        $_;
+    } @{$statement->{lines}};
+}
+
+# ------------------------------------------------------------------------------
+1;
+__END__
+
+=head1 NAME
+
+Fcm::Build::Fortran
+
+=head1 SYNOPSIS
+
+    use Fcm::Build::Fortran;
+    my $fortran_util = Fcm::Build::Fortran->new();
+    open(my($handle), '<', $path_to_a_fortran_source_file);
+    print($fortran_util->extract_interface($handle)); # prints interface
+    close($handle);
+
+=head1 DESCRIPTION
+
+A class to analyse Fortran source. Currently, it has a single method to extract
+the calling interfaces of top level subroutines and functions in a Fortran
+source.
+
+=head1 METHODS
+
+=over 4
+
+=item $class->new()
+
+Creates and returns an instance of this class.
+
+=item $instance->extract_interface($handle)
+
+Extracts the calling interfaces of top level subroutines and functions in a
+Fortran source that can be read from $handle. Returns an interface block as a
+list of lines.
+
+=back
+
+=head1 ACKNOWLEDGEMENT
+
+This module is inspired by the logic developed by the European Centre
+for Medium-Range Weather Forecasts (ECMWF).
+
+=head1 COPYRIGHT
+
+(C) Crown copyright Met Office. All rights reserved.
+
+=cut
Index: /OFFICIAL/FCM_V1.5/lib/Fcm/CLI/Config.pm
===================================================================
--- /OFFICIAL/FCM_V1.5/lib/Fcm/CLI/Config.pm	(revision 12)
+++ /OFFICIAL/FCM_V1.5/lib/Fcm/CLI/Config.pm	(revision 12)
@@ -0,0 +1,133 @@
+# ------------------------------------------------------------------------------
+# (C) Crown copyright Met Office. All rights reserved.
+# For further details please refer to the file COPYRIGHT.txt
+# which you should have received as part of this distribution.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+
+package Fcm::CLI::Config;
+
+use Fcm::CLI::Config::Default;
+use List::Util qw{first};
+use Scalar::Util qw{blessed};
+
+my $INSTANCE;
+
+################################################################################
+# Class method: returns an instance of this class
+sub instance {
+    my ($class, $args_ref) = @_;
+    if ($args_ref || !$INSTANCE) {
+        $INSTANCE = bless({
+            core_subcommands => [@Fcm::CLI::Config::Default::CORE_SUBCOMMANDS],
+            vc_subcommands   => [@Fcm::CLI::Config::Default::VC_SUBCOMMANDS],
+            (defined($args_ref) ? %{$args_ref} : ()), 
+        }, $class);
+    }
+    return $INSTANCE;
+}
+
+################################################################################
+# Returns a subcommand matching $key
+sub get_subcommand_of {
+    my ($self, $key) = @_;
+    if (blessed($key) && $key->isa('Fcm::CLI::Subcommand')) {
+        return first {"$_" eq "$key"} ($self->get_subcommands());
+    }
+    else {
+        return first {$_->has_a_name($key)} ($self->get_subcommands());
+    }
+}
+
+################################################################################
+# Returns the subcommands
+sub get_subcommands {
+    my ($self) = @_;
+    my @return = ($self->get_core_subcommands(), $self->get_vc_subcommands());
+    return (wantarray() ? @return : \@return);
+}
+
+################################################################################
+# Returns the core subcommands
+sub get_core_subcommands {
+    my ($self) = @_;
+    return (
+        wantarray() ? @{$self->{core_subcommands}} : $self->{core_subcommands}
+    );
+}
+
+################################################################################
+# Returns the subcommands that are relevant only with a VC system
+sub get_vc_subcommands {
+    my ($self) = @_;
+    return (wantarray() ? @{$self->{vc_subcommands}} : $self->{vc_subcommands});
+}
+
+1;
+__END__
+
+=head1 NAME
+
+Fcm::CLI::Config
+
+=head1 SYNOPSIS
+
+    use Fcm::CLI::Config;
+    $cli_config = Fcm::CLI::Config->instance();
+    $subcommand = $cli_config->get_subcommand_of($key);
+    @subcommands = $cli_config->get_subcommands();
+    @core_subcommands = $cli_config->get_core_subcommands();
+    @vc_subcommands = $cli_config->get_vc_subcommands();
+
+=head1 DESCRIPTION
+
+This class provides the configuration of the FCM command line interface.
+
+=head1 METHODS
+
+=over 4
+
+=item instance($arg_ref)
+
+Returns an instance of this class.
+
+Creates the instance on first call, or replaces it with a new one if $args_ref
+is defined in subsequent call. $args_ref should be a reference to a hash. The
+hash can contain I<core_subcommands> and I<vc_subcommands>. Each of these
+settings should point to an array reference containing L<Fcm::CLI::Subcommand>
+objects. If the setting is unspecified, it uses the default from
+L<Fcm::CLI::Config::Default|Fcm::CLI::Config::Default>.
+
+=item get_subcommand_of($key)
+
+Returns a L<Fcm::CLI::Subcommand|Fcm::CLI::Subcommand> object matching the
+search $key. Returns undef if there is no match.
+
+=item get_subcommands()
+
+Short-hand for:
+    ($self->get_core_subcommands(), $self->get_vc_subcommands())
+
+=item get_core_subcommands()
+
+Returns the core subcommands.
+
+=item get_vc_subcommands()
+
+Returns the subcommands that are relevant only in the presence of a VC system.
+
+=back
+
+=head1 SEE ALSO
+
+L<Fcm::CLI::Config::Default|Fcm::CLI::Config::Default>,
+L<Fcm::CLI::Invoker|Fcm::CLI::Invoker>,
+L<Fcm::CLI::Subcommand|Fcm::CLI::Subcommand>,
+L<Fcm::CLI::Option|Fcm::CLI::Option>
+
+=head1 COPYRIGHT
+
+E<169> Crown copyright Met Office. All rights reserved.
+
+=cut
Index: /OFFICIAL/FCM_V1.5/lib/Fcm/CLI/Exception.pm
===================================================================
--- /OFFICIAL/FCM_V1.5/lib/Fcm/CLI/Exception.pm	(revision 12)
+++ /OFFICIAL/FCM_V1.5/lib/Fcm/CLI/Exception.pm	(revision 12)
@@ -0,0 +1,42 @@
+# ------------------------------------------------------------------------------
+# (C) Crown copyright Met Office. All rights reserved.
+# For further details please refer to the file COPYRIGHT.txt
+# which you should have received as part of this distribution.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+
+package Fcm::CLI::Exception;
+use base qw{Fcm::Exception};
+
+1;
+__END__
+
+=head1 NAME
+
+Fcm::CLI::Exception
+
+=head1 SYNOPSIS
+
+    use Carp qw{croak};
+    use Fcm::CLI::Exception;
+    croak(Fcm::CLI::Exception->new({message => 'something is wrong'}));
+
+=head1 DESCRIPTION
+
+This class extends L<Fcm::Exception|Fcm::Exception>. This exception is thrown
+on errors associated with the command line interface.
+
+=head1 METHODS
+
+See L<Fcm::Exception|Fcm::Exception> for a list of methods.
+
+=head1 SEE ALSO
+
+L<Fcm::Exception|Fcm::Exception>
+
+=head1 COPYRIGHT
+
+E<169> Crown copyright Met Office. All rights reserved.
+
+=cut
Index: /OFFICIAL/FCM_V1.5/lib/Fcm/CLI/Invoker.pm
===================================================================
--- /OFFICIAL/FCM_V1.5/lib/Fcm/CLI/Invoker.pm	(revision 12)
+++ /OFFICIAL/FCM_V1.5/lib/Fcm/CLI/Invoker.pm	(revision 12)
@@ -0,0 +1,136 @@
+# ------------------------------------------------------------------------------
+# (C) Crown copyright Met Office. All rights reserved.
+# For further details please refer to the file COPYRIGHT.txt
+# which you should have received as part of this distribution.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+
+package Fcm::CLI::Invoker;
+
+use Carp qw{croak};
+use Fcm::CLI::Exception;
+
+################################################################################
+# Constructor
+sub new {
+    my ($class, $args_ref) = @_;
+    return bless({%{$args_ref}}, $class);
+}
+
+################################################################################
+# Returns the name of the (sub)command as given by the user
+sub get_command {
+    my ($self) = @_;
+    return $self->{command};
+}
+
+################################################################################
+# Returns a reference to a hash containing the options
+sub get_options {
+    my ($self) = @_;
+    return (wantarray() ? %{$self->{options}} : $self->{options});
+}
+
+################################################################################
+# Returns a reference to an array containing the arguments
+sub get_arguments {
+    my ($self) = @_;
+    return (wantarray() ? @{$self->{arguments}} : $self->{arguments});
+}
+
+################################################################################
+# Invokes the sub-system
+sub invoke {
+    my ($self) = @_;
+    my $message = "command not implemented\n";
+    $message .= sprintf("opts:");
+    for my $key (sort keys(%{$self->get_options()})) {
+        my $value = $self->get_options()->{$key};
+        $message .= sprintf(
+            " [%s=%s]",
+            $key,
+            ($value && ref($value) eq 'ARRAY' ? join(q{, }, @{$value}) : $value)
+        );
+    }
+    $message .= sprintf("\n");
+    $message .= sprintf("args: [%s]\n", join(q{] [}, $self->get_arguments()));
+    croak(Fcm::CLI::Exception->new({message => $message}));
+}
+
+1;
+__END__
+
+=head1 NAME
+
+Fcm::CLI::Invoker
+
+=head1 SYNOPSIS
+
+    use Fcm::CLI::Invoker;
+    $invoker = Fcm::CLI::Invoker->new({
+        command   => $command,
+        options   => \%options,
+        arguments => $arguments,
+    });
+    $invoker->invoke();
+
+=head1 DESCRIPTION
+
+This is the base class for an invoker of a FCM sub-system from the CLI.
+Sub-classes should override the invoke() method.
+
+=head1 METHODS
+
+=over 4
+
+=item new($args_ref)
+
+Constructor. It accepts a hash reference as an argument. The element I<command>
+should be set to the actual (sub)command as specified by the user. The element
+I<options> should be a reference to a hash containing the specified command line
+options. The element I<arguments> should be a reference to an array containing
+the remaining command line arguments.
+
+=item get_command()
+
+Returns the actual (sub)command as specified by the user.
+
+=item get_options()
+
+Returns a hash containing the specified command line options. In scalar context,
+returns a reference to the hash.
+
+=item get_arguments()
+
+Returns an array containing the (remaining) command line arguments. In scalar
+context, returns a reference to the array.
+
+=item invoke()
+
+Sub-classes should override this method. Calling the method in this base
+class causes the system to croak() with a
+L<Fcm::CLI::Exception|Fcm::CLI::Exception>.
+
+=back
+
+=head1 DIAGNOSTICS
+
+=over 4
+
+=item L<Fcm::CLI::Exception|Fcm::CLI::Exception>
+
+The C<invoke()> croak() with this exception.
+
+=back
+
+=head1 SEE ALSO
+
+L<Fcm::CLI::Exception|Fcm::CLI::Exception>,
+L<Fcm::CLI::Subcommand|Fcm::CLI::Subcommand>
+
+=head1 COPYRIGHT
+
+E<169> Crown copyright Met Office. All rights reserved.
+
+=cut
Index: /OFFICIAL/FCM_V1.5/lib/Fcm/CLI/Option.pm
===================================================================
--- /OFFICIAL/FCM_V1.5/lib/Fcm/CLI/Option.pm	(revision 12)
+++ /OFFICIAL/FCM_V1.5/lib/Fcm/CLI/Option.pm	(revision 12)
@@ -0,0 +1,183 @@
+# ------------------------------------------------------------------------------
+# (C) Crown copyright Met Office. All rights reserved.
+# For further details please refer to the file COPYRIGHT.txt
+# which you should have received as part of this distribution.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+
+package Fcm::CLI::Option;
+
+use constant NO_ARG     => 0;
+use constant SCALAR_ARG => 1;
+use constant ARRAY_ARG  => 2;
+use constant HASH_ARG   => 3;
+use constant ARG_STRING_SUFFIX_FOR => (q{}, q{=s}, q{=s@}, q{=s%});
+
+################################################################################
+# Constructor
+sub new {
+    my ($class, $args_ref) = @_;
+    return bless({%{$args_ref}}, $class);
+}
+
+################################################################################
+# Methods: get_*
+for my $key (
+    # Returns the delimiter of this option, if it is an array
+    'delimiter',
+    # Returns the description of this option
+    'description',
+    # Returns the (long) name of this option
+    'name',
+) {
+    no strict qw{refs};
+    my $getter = "get_$key";
+    *$getter = sub {
+        my ($self) = @_;
+        return $self->{$key};
+    }
+}
+
+################################################################################
+# Returns the letter of this option
+sub get_letter {
+    my ($self) = @_;
+    if (defined($self->{letter})) {
+        return substr($self->{letter}, 0, 1);
+    }
+    else {
+        return;
+    }
+}
+
+################################################################################
+# Returns whether the current option has no, scalar, array or hash arguments
+sub has_arg {
+    my ($self) = @_;
+    return (defined($self->{has_arg}) ? $self->{has_arg} : $self->NO_ARG);
+}
+
+################################################################################
+# Returns true if this option is associated with help
+sub is_help {
+    my ($self) = @_;
+    return $self->{is_help};
+}
+
+################################################################################
+# Returns an option string/reference pair for Getopt::Long::GetOptions
+sub get_arg_for_getopt_long {
+    my ($self) = @_;
+    my $option_string
+        = $self->get_name()
+          . ($self->get_letter() ? q{|} . $self->get_letter() : q{})
+          . (ARG_STRING_SUFFIX_FOR)[$self->has_arg()]
+          ;
+    return $option_string;
+}
+
+1;
+__END__
+
+=head1 NAME
+
+Fcm::CLI::Option
+
+=head1 SYNOPSIS
+
+    use Fcm::CLI::Option;
+    $option = Fcm::CLI::Option->new({
+        name        => 'name',
+        letter      => 'n',
+        has_arg     => Fcm::CLI::Option->SCALAR_ARG,
+        is_help     => 1,
+        description => 'an example option',
+    });
+
+    # time passes ...
+    use Getopt::Long qw{GetOptions};
+    $success = GetOptions(
+        \%hash,
+        $option->get_arg_for_getopt_long(), # ('name|n=s')
+        # and other options ...
+    );
+    $option_value = $option->get_value();
+
+=head1 DESCRIPTION
+
+An object of this class represents a CLI option.
+
+=head1 METHODS
+
+=over 4
+
+=item new($args_ref)
+
+Constructor.
+
+=item get_arg_for_getopt_long()
+
+Returns an option string for this option that is suitable for use as arguments
+to L<Getopt::Long|Getopt::Long>.
+
+=item get_description()
+
+Returns a description of this option.
+
+=item get_delimiter()
+
+Returns the delimiter of this option. This is only relevant if has_arg() is
+equal to C<ARRAY_ARG>. If set, the argument for this option should be re-grouped
+using this delimiter.
+
+=item get_name()
+
+Returns the (long) name of this option.
+
+=item get_letter()
+
+Returns the option letter of this option.
+
+=item has_arg()
+
+Returns whether this option has no, scalar, array or hash arguments. See
+L</CONSTANTS> for detail.
+
+=item is_help()
+
+Returns true if this option is associated with help.
+
+=back
+
+=head1 CONSTANTS
+
+=over 4
+
+=item NO_ARG
+
+An option has no argument. (Default)
+
+=item SCALAR_ARG
+
+An option has a single scalar argument.
+
+=item ARRAY_ARG
+
+An option has multiple arguments, which can be placed in an array.
+
+=item HASH_ARG
+
+An option has multiple arguments, which can be placed in an hash.
+
+=back
+
+=head1 SEE ALSO
+
+L<Getopt::Long|Getopt::Long>
+
+=head1 COPYRIGHT
+
+E<169> Crown copyright Met Office. All rights reserved.
+
+=cut
Index: /OFFICIAL/FCM_V1.5/lib/Fcm/CLI/Subcommand.pm
===================================================================
--- /OFFICIAL/FCM_V1.5/lib/Fcm/CLI/Subcommand.pm	(revision 12)
+++ /OFFICIAL/FCM_V1.5/lib/Fcm/CLI/Subcommand.pm	(revision 12)
@@ -0,0 +1,245 @@
+# ------------------------------------------------------------------------------
+# (C) Crown copyright Met Office. All rights reserved.
+# For further details please refer to the file COPYRIGHT.txt
+# which you should have received as part of this distribution.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+
+package Fcm::CLI::Subcommand;
+
+use Carp qw{croak};
+use Fcm::CLI::Exception;
+use Fcm::Util::ClassLoader;
+
+################################################################################
+# Constructor
+sub new {
+    my ($class, $args_ref) = @_;
+    return bless({%{$args_ref}}, $class);
+}
+
+################################################################################
+# Methods: get_*
+for my $key (
+    # Returns the long description of this subcommand
+    'description',
+    # Returns the class of the invoker of this subcommand
+    'invoker_class',
+    # Returns the extra config to be given to the invoker of this subcommand
+    'invoker_config',
+    # Returns the names of this subcommand
+    'names',
+    # Returns the options of this subcommand
+    'options',
+    # Returns the synopsis of this subcommand
+    'synopsis',
+    # Returns the usage of this subcommand
+    'usage',
+) {
+    no strict qw{refs};
+    my $getter = "get_$key";
+    *$getter = sub {
+        my ($self) = @_;
+        if (defined($self->{$key})) {
+            if (ref($self->{$key}) eq 'ARRAY') {
+                return (wantarray() ? @{$self->{$key}} : $self->{$key});
+            }
+            else {
+                return $self->{$key};
+            }
+        }
+        else {
+            return;
+        }
+    }
+}
+
+################################################################################
+# Returns true if this subcommand represents a command in the CM sub-system
+sub is_vc {
+    my ($self) = @_;
+    return $self->{is_vc};
+}
+
+################################################################################
+# Returns true if $string matches a name of this subcommand
+sub has_a_name {
+    my ($self, $string) = @_;
+    if ($self->get_names() && ref($self->get_names()) eq 'ARRAY') {
+        my %name_of = map {$_, 1} @{$self->get_names()};
+        return exists($name_of{$string});
+    }
+    else {
+        return;
+    }
+}
+
+################################################################################
+# Invokes this subcommand based on current @ARGV
+sub get_invoker {
+    my ($self, $command) = @_;
+    my %options = ();
+    if (($self->get_options())) {
+        my $problem = q{};
+        local($SIG{__WARN__}) = sub {
+            ($problem) = @_;
+        };
+        my $success = GetOptions(
+            \%options,
+            (map {$_->get_arg_for_getopt_long()} ($self->get_options())),
+        );
+        if (!$success) {
+            croak(Fcm::CLI::Exception->new({message => sprintf(
+                "%s: option parse failed: %s", $command, $problem,
+            )}));
+        }
+    }
+    my $invoker_class
+        = $self->get_invoker_class() ? $self->get_invoker_class()
+        :                              'Fcm::CLI::Invoker'
+        ;
+    Fcm::Util::ClassLoader::load($invoker_class);
+    my $invoker = $invoker_class->new({
+        command   => $command,
+        options   => \%options,
+        arguments => [@ARGV],
+    });
+    return $invoker;
+}
+
+################################################################################
+# Returns a simple string representation of this subcommand
+sub as_string {
+    my ($self) = @_;
+    # FIXME: can do with using Text::Template or Perl6::Form
+    if (
+           $self->get_names()
+        && ref($self->get_names()) eq 'ARRAY'
+        && @{$self->get_names()}
+    ) {
+        my @names = $self->get_names();
+        my $return = $names[0];
+        for my $i (1 .. $#names) {
+            if ($names[$i]) {
+                $return
+                    .= $i == 1       ? q{ (} . $names[$i]
+                    :                  q{, } . $names[$i]
+                    ;
+            }
+            if ($i == $#names) {
+                $return .= q{)};
+            }
+        }
+        return $return;
+    }
+    else {
+        return q{};
+    }
+}
+
+1;
+__END__
+
+=head1 NAME
+
+Fcm::CLI::Subcommand
+
+=head1 SYNOPSIS
+
+    use Fcm::CLI::Subcommand;
+    $subcommand = Fcm::CLI::Subcommand->new({
+        names          => ['build', 'bld'],
+        options        => [
+            Fcm::CLI::Option->new(
+                # ... some arguments ...
+            ),
+            # ... more options
+        ],
+        synopsis       => 'invokes the build system',
+        description    => $description,
+        usage          => '[OPTIONS] [CONFIG]',
+        invoker_class  => $invoker_class,
+        invoker_config => {
+            option1 => $option1,
+            # ... more options
+        },
+    });
+    $boolean = $subcommand->has_a_name($string);
+    $invoker_class = $subcommand->get_invoker_class();
+
+=head1 DESCRIPTION
+
+An object of this class is used to store the configuration of a subcommand of
+the FCM CLI.
+
+=head1 METHODS
+
+=over 4
+
+=item new($args_ref)
+
+Constructor.
+
+=item get_description()
+
+Returns the long description of this subcommand.
+
+=item get_invoker_class()
+
+Returns the invoker class of this subcommand, which should be a sub-class of
+L<Fcm::CLI::Invoker|Fcm::CLI::Invoker>.
+
+=item get_invoker_cconfig()
+
+Returns a reference to a hash containing the extra configuration to be given to
+the constructor of the invoker of this subcommand.
+
+=item get_names()
+
+Returns an array containing the names of this subcommand.
+
+=item get_options()
+
+Returns an array containing the options of this subcommand. Each element of
+the array should be a L<Fcm::CLI::Option|Fcm::CLI::Option> object.
+
+=item get_synopsis()
+
+Returns a short synopsis of this subcommand.
+
+=item get_usage()
+
+Returns a short usage statement of this subcommand.
+
+=item is_vc()
+
+Returns true if this subcommand represents commands in the underlying VC system.
+
+=item has_a_name($string)
+
+Returns true if a name in C<$self-E<gt>get_names()> matches $string.
+
+=back
+
+=head1 DIAGNOSTICS
+
+=over 4
+
+=item L<Fcm::CLI::Exception|Fcm::CLI::Exception>
+
+The invoke() method may croak() with this exception.
+
+=back
+
+=head1 SEE ALSO
+
+L<Fcm::CLI::Exception|Fcm::CLI::Exception>,
+L<Fcm::CLI::Invoker|Fcm::CLI::Invoker>,
+L<Fcm::CLI::Option|Fcm::CLI::Option>
+
+=head1 COPYRIGHT
+
+E<169> Crown copyright Met Office. All rights reserved.
+
+=cut
Index: /OFFICIAL/FCM_V1.5/lib/Fcm/CLI/fcm-add.pod
===================================================================
--- /OFFICIAL/FCM_V1.5/lib/Fcm/CLI/fcm-add.pod	(revision 12)
+++ /OFFICIAL/FCM_V1.5/lib/Fcm/CLI/fcm-add.pod	(revision 12)
@@ -0,0 +1,22 @@
+=head1 NAME
+
+fcm add
+
+=head1 SYNOPSIS
+
+    fcm add [options] [args]
+
+=head1 OPTIONS
+
+=over 4
+
+=item -c [--check]
+
+Check for any files or directories reported by "L<svn|svn> status" as not under
+version control and add them.
+
+=back
+
+For other options, see output of "L<svn|svn> help add".
+
+=cut
Index: /OFFICIAL/FCM_V1.5/lib/Fcm/CLI/fcm-branch.pod
===================================================================
--- /OFFICIAL/FCM_V1.5/lib/Fcm/CLI/fcm-branch.pod	(revision 12)
+++ /OFFICIAL/FCM_V1.5/lib/Fcm/CLI/fcm-branch.pod	(revision 12)
@@ -0,0 +1,173 @@
+=head1 NAME
+
+fcm branch (br)
+
+=head1 SYNOPSIS
+
+Create, delete or display information of a branch, or list branches in a
+project.
+
+    fcm branch [--info] [OPTIONS] [TARGET]
+    fcm branch --delete [OPTIONS] [TARGET]
+    fcm branch --create [OPTIONS] [SOURCE]
+    fcm branch --list   [OPTIONS] [SOURCE]
+
+=head1 ARGUMENTS
+
+TARGET (and SOURCE) can be an URL or a Subversion working copy. Otherwise,
+the current working directory must be a working copy. For --info and
+--delete, the specified URL (or the URL of the working copy) must be a URL
+under a valid branch in a standard FCM project. For --create and --list, it
+must be a URL under a standard FCM project.
+
+=head1 OPTIONS
+
+=over 4
+
+=item --info or -i
+
+Display information about a branch. This is the default option if --create,
+--delete and --list are not specified.
+
+=item --delete or -d
+
+Delete a branch.
+
+=item --create or -c
+
+Create a new branch from SOURCE. The --name option must be used to specify a
+short name for the new branch.
+
+=item --list or -l
+
+List all the branches owned by the current user in SOURCE. If the --user option
+is specified with a list of users, list all the branches owned by these users
+instead of the current user.
+
+=back
+
+Valid options with --info and --delete:
+
+=over 4
+
+=item -v [--verbose]
+
+Print extra information.
+
+=item -a [--show-all]
+
+Set --show-children, --show-other and --show-siblings.
+
+=item --show-children
+
+Report children of the current branch.
+
+=item --show-other
+
+Report custom/ reverse merges into the current branch.
+
+=item --show-siblings
+
+Report merges with siblings of the current branch.
+
+=back
+
+Valid options with --delete and --create:
+
+=over 4
+
+=item --non-interactive
+
+Do no interactive prompting. This option implies --svn-non-interactive.
+
+=item --password arg
+
+Specify a password for write access to the repository.
+
+=item --svn-non-interactive
+
+Do no interactive prompting at commit time. This option is implied by
+--non-interactive.
+
+=back
+
+Valid options with --create and --list:
+
+=over 4
+
+=item -r [--revision] arg
+
+Specify the operative revision of the SOURCE for creating the branch.
+
+=back
+
+Valid options with --create:
+
+=over 4
+
+=item --branch-of-branch
+
+If this option is specified and the SOURCE is a branch, it will create a new
+branch from the SOURCE branch. Otherwise, the branch is created from the trunk.
+
+=item -k [--ticket] arg
+
+Specify one (or more) Trac ticket. If specified, the command will add to the
+commit log the line "Relates to ticket #<ticket>". Multiple tickets can be set
+by specifying this option multiple times, or by specifying the tickets in a
+comma-separated list.
+
+=item -n [--name] arg
+
+Specify a short name for the branch, which should contain only characters in the
+set [A-Za-z0-9_-.].
+
+=item --rev-flag arg
+
+Specify a flag for determining the prefix of the branch name. The flag can be
+the the string "NORMAL", "NUMBER" or "NONE".  "NORMAL" is the default behaviour,
+in which the branch name will be prefixed with a Subversion revision number if
+the revision is not associated with a registered FCM revision keyword. If the
+revision is registered with a FCM revision keyword, the keyword will be used in
+place of the number. If "NUMBER" is specified, the branch name will always be
+prefixed with a Subversion revision number. If "NONE" is specified, the branch
+name will not be prefixed by a revision number or keyword.
+
+=item -t [--type] arg
+
+Specify the type of the branch to be created. It must be one of the following:
+
+ DEV::USER [DEV, USER] - a development branch for the user
+ DEV::SHARE [SHARE]    - a shared development branch
+ TEST::USER [TEST]     - a test branch for the user
+ TEST::SHARE           - a shared test branch
+ PKG::USER [PKG]       - a package branch for the user
+ PKG::SHARE            - a shared package branch
+ PKG::CONFIG [CONFIG]  - a configuration branch
+ PKG::REL [REL]        - a release branch
+
+If not specified, the default is to create a development branch for the current
+user, i.e.  DEV::USER.
+
+=back
+
+Valid options with --list:
+
+=over 4
+
+=item -a [--show-all]
+
+Print all branches.
+
+=item -u [--user] arg
+
+Specify a colon-separated list of users. List branches owned by these users
+instead of the current user.
+
+=item -v [--verbose]
+
+Print Subversion URL instead of FCM URL keywords.
+
+=back
+
+=cut
Index: /OFFICIAL/FCM_V1.5/lib/Fcm/CLI/fcm-commit.pod
===================================================================
--- /OFFICIAL/FCM_V1.5/lib/Fcm/CLI/fcm-commit.pod	(revision 12)
+++ /OFFICIAL/FCM_V1.5/lib/Fcm/CLI/fcm-commit.pod	(revision 12)
@@ -0,0 +1,31 @@
+=head1 NAME
+
+fcm commit (ci)
+
+=head1 SYNOPSIS
+
+    fcm commit [OPTIONS] [PATH ...]
+
+Send changes from your working copy to the repository. Invoke your favourite
+editor to prompt you for a commit log message. Update your working copy
+following the commit.
+
+=head1 OPTIONS
+
+=over 4
+
+=item --dry-run
+
+Allows you to add to the commit message without committing.
+
+=item --svn-non-interactive
+
+Do no interactive prompting at commit time.
+
+=item --password arg
+
+Specify a password ARG.
+
+=back
+
+=cut
Index: /OFFICIAL/FCM_V1.5/lib/Fcm/CLI/fcm-conflicts.pod
===================================================================
--- /OFFICIAL/FCM_V1.5/lib/Fcm/CLI/fcm-conflicts.pod	(revision 12)
+++ /OFFICIAL/FCM_V1.5/lib/Fcm/CLI/fcm-conflicts.pod	(revision 12)
@@ -0,0 +1,17 @@
+=head1 NAME
+
+fcm conflicts (cf)
+
+=head1 SYNOPSIS
+
+Use graphical tool to resolve any conflicts within your working copy.
+
+    fcm conflicts [PATH]
+
+=head1 ARGUMENTS
+
+Invoke a graphical merge tool to help you resolve conflicts in your working copy
+at PATH. It prompts you to run "L<svn|svn> resolved" each time you have resolved
+the conflicts in a text file.
+
+=cut
Index: /OFFICIAL/FCM_V1.5/lib/Fcm/CLI/fcm-delete.pod
===================================================================
--- /OFFICIAL/FCM_V1.5/lib/Fcm/CLI/fcm-delete.pod	(revision 12)
+++ /OFFICIAL/FCM_V1.5/lib/Fcm/CLI/fcm-delete.pod	(revision 12)
@@ -0,0 +1,22 @@
+=head1 NAME
+
+fcm delete (del, remove, rm)
+
+=head1 SYNOPSIS
+
+    fcm delete [options] [args]
+
+=head1 OPTIONS
+
+=over 4
+
+=item -c [--check]
+
+Check for any files or directories reported by "L<svn|svn> status" as missing
+and schedule them for removal.
+
+=back
+
+For other options, see output of "L<svn|svn> help delete".
+
+=cut
Index: /OFFICIAL/FCM_V1.5/lib/Fcm/CLI/fcm-diff.pod
===================================================================
--- /OFFICIAL/FCM_V1.5/lib/Fcm/CLI/fcm-diff.pod	(revision 12)
+++ /OFFICIAL/FCM_V1.5/lib/Fcm/CLI/fcm-diff.pod	(revision 12)
@@ -0,0 +1,79 @@
+=head1 NAME
+
+fcm diff (di)
+
+=head1 SYNOPSIS
+
+    1. fcm diff --branch [OPTIONS] [TARGET]
+    2. fcm diff [OPTIONS] [ARGS]
+
+=over 4
+
+=item 1.
+
+Show differences relative to the base of the target branch, i.e. the changes
+available for merging from the target branch into its parent. If TARGET is
+specified, it must either be a URL or a working copy.  Otherwise, the target is
+the the current directory which must be a working copy. The target URL must be a
+branch in a standard FCM project.
+
+=item 2.
+
+See description of "L<svn|svn> diff" below.
+
+=back
+
+=head1 OPTIONS
+
+Valid options with --branch:
+
+=over 4
+
+=item --diff-cmd arg
+
+As described below in the help for "L<svn|svn> diff".
+
+=item -g [--graphical]
+
+As described below.
+
+=item --summarise
+
+As described below
+
+=item --summarize
+
+As described below in the help for "L<svn|svn> diff".
+
+=item -t [--trac]
+
+If TARGET is a URL, use Trac to display the diff.
+
+=item --wiki
+
+If TARGET is a URL, print Trac link for the diff.
+
+=item -x [--extensions] arg
+
+As described below in the help for "L<svn|svn> diff".
+
+=back
+
+Other options:
+
+=over 4
+
+=item -g [--graphical]
+
+Use a graphical diff tool to display the differences. This option should not be
+used in combination with --diff-cmd.
+
+=item --summarise
+
+Same as --summarize as described below.
+
+=back
+
+For other options, see output of "L<svn|svn> help diff".
+
+=cut
Index: /OFFICIAL/FCM_V1.5/lib/Fcm/CLI/fcm-merge.pod
===================================================================
--- /OFFICIAL/FCM_V1.5/lib/Fcm/CLI/fcm-merge.pod	(revision 12)
+++ /OFFICIAL/FCM_V1.5/lib/Fcm/CLI/fcm-merge.pod	(revision 12)
@@ -0,0 +1,71 @@
+=head1 NAME
+
+fcm merge
+
+=head1 SYNOPSIS
+
+Merge changes from a source into your working copy.
+
+    1. fcm merge SOURCE
+    2. fcm merge --custom  --revision N[:M] SOURCE
+       fcm merge --custom  URL[\@REV1] URL[\@REV2]
+    3. fcm merge --reverse --revision [M:]N
+
+=over 4
+
+=item 1.
+
+If neither --custom nor --reverse is specified, the command merges changes
+automatically from SOURCE into your working copy. SOURCE must be a valid
+URL[@REV] of a branch in a standard FCM project. The base of the merge will be
+calculated automatically based on the common ancestor and latest merge
+information between the SOURCE and the branch of the working copy.
+
+=item 2.
+
+If --custom is specified, the command can be used in two forms.
+  
+In the first form, it performs a custom merge from the specified changeset(s) of
+SOURCE into your working copy. SOURCE must be a valid URL[@REV] of a branch in
+a standard FCM project. If a single revision is specified, the merge delta is (N
+- 1):N of SOURCE. Otherwise, the merge delta, is N:M of SOURCE, where N < M.
+     
+In the second form, it performs a custom merge using the delta between the two
+specified branch URLs. For each URL, if a peg revision is not specified, the
+command will peg the URL with its last changed revision.
+
+=item 3.
+
+If --reverse is specified, the command performs a reverse merge of the
+changeset(s) specified by the --revision option. If a single revision is
+specified, the merge delta is N:(N - 1). Otherwise, the merge delta is M:N,
+where M > N. Note that you do not have to specify a SOURCE for a reverse merge,
+because the SOURCE should always be the branch your working copy is pointing to.
+  
+=back
+
+The command provide a commit log message template following the merge.
+
+=head1 OPTIONS
+
+=over 4
+
+=item --dry-run
+
+Try operation but make no changes.
+
+=item --non-interactive
+
+Do no interactive prompting.
+
+=item -r [--revision] arg
+
+Specify a (range of) revision number(s).
+
+=item --verbose
+
+Print extra information.
+
+=back
+
+=cut
Index: /OFFICIAL/FCM_V1.5/lib/Fcm/CLI/fcm-mkpatch.pod
===================================================================
--- /OFFICIAL/FCM_V1.5/lib/Fcm/CLI/fcm-mkpatch.pod	(revision 12)
+++ /OFFICIAL/FCM_V1.5/lib/Fcm/CLI/fcm-mkpatch.pod	(revision 12)
@@ -0,0 +1,60 @@
+=head1 NAME
+
+fcm mkpatch
+
+=head1 SYNOPSIS
+
+mkpatch: Create patches from specified revisions of a URL
+
+    fcm mkpatch [OPTIONS] URL [OUTDIR]
+
+Create patches from specified revisions of the specified URL. If OUTDIR is
+specified, the output is sent to OUTDIR. Otherwise, the output will be sent to a
+default location in the current directory ($PWD/fcm-mkpatch-out). The output
+directory will contain the patch for each revision as well as a script for
+importing the patch.
+
+A warning is given if the URL is not of a branch in a FCM project or if it is a
+sub-directory of a branch.
+
+=head1 OPTIONS
+
+=over 4
+
+=item --exclude arg
+
+Exclude a path in the URL. Multiple paths can be specified by using a
+colon-separated list of paths, or by specifying this option multiple times.
+
+The specified path must be a relative path of the URL. Glob patterns such as *
+and ? are acceptable. Changes in an excluded path will not be considered in the
+patch. A changeset containing changes only in the excluded path will not be
+considered at all.
+
+=item --organisation arg
+
+This option can be used to specify the name of your organisation.
+
+The command will attempt to parse the commit log message for each revision in
+the patch. It will remove all merge templates, replace Trac links with a
+modified string, and add information about the original changeset. If you
+specify the name of your organisation, it will replace Trac links such as
+"ticket:123" with "$organisation_ticket:123", and report the original changeset
+with a message such as "$organisation_changeset:1000".  If the organisation
+name is not specified then it defaults to "original".
+
+=item -r [--revision] arg
+
+Specify a revision number or a revision number range.
+
+If a revision is specified with the --revision option, it will attempt to create
+a patch based on the changes at that revision. If a revision is not specified,
+it will attempt to create a patch based on the changes at the HEAD revision. If
+a revision range is specified, it will attempt to create a patch for each
+revision in that range (including the change in the lower range) where changes
+have taken place in the URL. No output will be written if there is no change in
+the given revision (range).
+
+=back
+
+=cut
Index: /OFFICIAL/FCM_V1.5/lib/Fcm/CLI/fcm-switch.pod
===================================================================
--- /OFFICIAL/FCM_V1.5/lib/Fcm/CLI/fcm-switch.pod	(revision 12)
+++ /OFFICIAL/FCM_V1.5/lib/Fcm/CLI/fcm-switch.pod	(revision 12)
@@ -0,0 +1,14 @@
+=head1 NAME
+
+fcm switch (sw)
+
+=head1 SYNOPSIS
+
+    1. switch URL [PATH]
+    2. switch --relocate FROM TO [PATH...]
+
+Note: if --relocate is not specified, "fcm switch" will only support the options
+--non-interactive, -r [--revision] and -q [--quiet]. For detail, see the output
+of "L<svn|svn> help switch".
+
+=cut
Index: /OFFICIAL/FCM_V1.5/lib/Fcm/CLI/fcm-update.pod
===================================================================
--- /OFFICIAL/FCM_V1.5/lib/Fcm/CLI/fcm-update.pod	(revision 12)
+++ /OFFICIAL/FCM_V1.5/lib/Fcm/CLI/fcm-update.pod	(revision 12)
@@ -0,0 +1,14 @@
+=head1 NAME
+
+fcm update (up)
+
+=head1 SYNOPSIS
+
+Bring changes from the repository into the working copy.
+
+    usage: update [PATH...]
+
+Note: "fcm update" only supports --non-interactive, -r [--revision] arg and -q
+[--quiet]. For detail, see the output of "L<svn|svn> help update".
+
+=cut
Index: /OFFICIAL/FCM_V1.5/lib/Fcm/Interactive/InputGetter.pm
===================================================================
--- /OFFICIAL/FCM_V1.5/lib/Fcm/Interactive/InputGetter.pm	(revision 12)
+++ /OFFICIAL/FCM_V1.5/lib/Fcm/Interactive/InputGetter.pm	(revision 12)
@@ -0,0 +1,122 @@
+# ------------------------------------------------------------------------------
+# (C) Crown copyright Met Office. All rights reserved.
+# For further details please refer to the file COPYRIGHT.txt
+# which you should have received as part of this distribution.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+
+package Fcm::Interactive::InputGetter;
+
+use Carp qw{croak};
+
+################################################################################
+# Constructor
+sub new {
+    my ($class, $args_ref) = @_;
+    return bless({%{$args_ref}}, $class);
+}
+
+################################################################################
+# Methods: get_*
+for my $key (
+    ############################################################################
+    # Returns the title of the prompt
+    'title',
+    ############################################################################
+    # Returns the message of the prompt
+    'message',
+    ############################################################################
+    # Returns the of the prompt
+    'type',
+    ############################################################################
+    # Returns the default return value
+    'default',
+) {
+    no strict qw{refs};
+    my $getter = "get_$key";
+    *$getter = sub {
+        my ($self) = @_;
+        return $self->{$key};
+    }
+}
+
+################################################################################
+# Invokes the getter
+sub invoke {
+    my ($self) = @_;
+    croak("Fcm::Interactive::InputGetter->invoke() not implemented.");
+}
+
+1;
+__END__
+
+=head1 NAME
+
+Fcm::Interactive::TxtInputGetter
+
+=head1 SYNOPSIS
+
+    use Fcm::Interactive::TxtInputGetter;
+    $answer = Fcm::Interactive::get_input(
+        title   => 'My title',
+        message => 'Would you like to ...?',
+        type    => 'yn',
+        default => 'n',
+    );
+
+=head1 DESCRIPTION
+
+An object of this abstract class is used by
+L<Fcm::Interactive|Fcm::Interactive> to get a user reply.
+
+=head1 METHODS
+
+=over 4
+
+=item new($args_ref)
+
+Constructor, normally invoked via L<Fcm::Interactive|Fcm::Interactive>.
+
+Input options are: I<title>, for a short title of the prompt, I<message>, for
+the message prompt, I<type> for the prompt type, and I<default> for the default
+value of the return value.
+
+Prompt type can be YN (yes or no), YNA (yes, no or all) or input (for an input
+string).
+
+=item get_title()
+
+Returns the title of the prompt.
+
+=item get_message()
+
+Returns the message of the prompt.
+
+=item get_type()
+
+Returns the type of the prompt, can be YN (yes or no), YNA (yes, no or all) or
+input (for an input string).
+
+=item get_default()
+
+Returns the default return value of invoke().
+
+=item invoke()
+
+Gets an input string from the user, and returns it. Sub-classes must override
+this method.
+
+=back
+
+=head1 SEE ALSO
+
+L<Fcm::Interactive|Fcm::Interactive>,
+L<Fcm::Interactive::TxtInputGetter|Fcm::Interactive::TxtInputGetter>,
+L<Fcm::Interactive::GUIInputGetter|Fcm::Interactive::GUIInputGetter>
+
+=head1 COPYRIGHT
+
+E<169> Crown copyright Met Office. All rights reserved.
+
+=cut
Index: /OFFICIAL/FCM_V1.5/lib/Fcm/Keyword/Config.pm
===================================================================
--- /OFFICIAL/FCM_V1.5/lib/Fcm/Keyword/Config.pm	(revision 12)
+++ /OFFICIAL/FCM_V1.5/lib/Fcm/Keyword/Config.pm	(revision 12)
@@ -0,0 +1,143 @@
+# ------------------------------------------------------------------------------
+# (C) Crown copyright Met Office. All rights reserved.
+# For further details please refer to the file COPYRIGHT.txt
+# which you should have received as part of this distribution.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+
+package Fcm::Keyword::Config;
+
+use Carp;
+use Fcm::Keyword::Entries;
+use Fcm::Keyword::Exception;
+use Fcm::Util::ClassLoader;
+
+our %CONFIG_OF = (
+    LOCATION_ENTRIES => {
+        entry_class => 'Fcm::Keyword::Entry::Location',
+        loaders => [
+            {
+                class => 'Fcm::Keyword::Loader::Config::Location',
+            },
+        ],
+    },
+    REVISION_ENTRIES => {
+        entry_class => 'Fcm::Keyword::Entry',
+        loaders => [
+            {
+                class   => 'Fcm::Keyword::Loader::Config::Revision',
+                options => [{key => 'namespace', valuekey => 'key'}],
+            },
+            {
+                class   => 'Fcm::Keyword::Loader::VC::Revision',
+                options => [{key => 'source', valuekey => 'value'}],
+            },
+        ],
+    },
+);
+
+################################################################################
+# Returns a Fcm::Keyword::Entries object for given configuration
+sub get_entries {
+    my ($context, $args_ref) = @_;
+    if (!exists($CONFIG_OF{$context})) {
+        croak(Fcm::Keyword::Exception->new({message => sprintf(
+            "%s: keyword configuration not found", $context,
+        )}));
+    }
+    my $config_ref = $CONFIG_OF{$context};
+    my @loaders;
+    if (exists($config_ref->{loaders})) {
+        for my $loader_config (@{$config_ref->{loaders}}) {
+            my $class = $loader_config->{class};
+            Fcm::Util::ClassLoader::load($class);
+            my %options;
+            if (exists($loader_config->{options})) {
+                for my $option_ref (@{$loader_config->{options}}) {
+                    my $key = $option_ref->{key};
+                    my $value;
+                    if (exists($option_ref->{value})) {
+                        $value = $option_ref->{value};
+                    }
+                    elsif (
+                           exists($option_ref->{valuekey})
+                        && $args_ref
+                        && ref($args_ref) eq 'HASH'
+                        && exists($args_ref->{$option_ref->{valuekey}})
+                    ) {
+                        $value = $args_ref->{$option_ref->{valuekey}};
+                    }
+                    $options{$key} = $value;
+                }
+            }
+            push @loaders, $class->new(\%options);
+        }
+    }
+    my %entries_options = (
+        (@loaders ? (loaders => \@loaders) : ()),
+        (
+            exists($config_ref->{entry_class})
+            ? (entry_class => $config_ref->{entry_class})
+            : ()
+        ),
+    );
+    return Fcm::Keyword::Entries->new(\%entries_options);
+}
+
+1;
+__END__
+
+=head1 NAME
+
+Fcm::Keyword::Config
+
+=head1 SYNOPSIS
+
+    use Fcm::Keyword::Config;
+
+=head1 DESCRIPTION
+
+This module stores the default configuration used by modules in the
+L<Fcm::Keyword> family.
+
+=head1 FUNCTIONS
+
+=over 4
+
+=item get_entries($context,$args_ref)
+
+Returns a L<Fcm::Keyword::Entries|Fcm::Keyword::Entries> object for a given
+$context. If there is no matching $context in the configuration, croak() with a
+L<Fcm::Keyword::Exception|Fcm::Keyword::Exception>. $args_ref is an optional
+argument, which should be a reference to a hash containing a I<key> and a
+I<value> element. It can be used by this function to set up the constructor
+options in the loaders of the returned
+L<Fcm::Keyword::Entries|Fcm::Keyword::Entries> object.
+
+=back
+
+=head1 DIAGNOSTICS
+
+=head1 TO DO
+
+Allow configuration to be changed in runtime.
+
+Convert this module to OO?
+
+Separate configuration from logic if this module becomes any bigger.
+
+Unit tests.
+
+=head1 SEE ALSO
+
+L<Fcm::Keyword|Fcm::Keyword>,
+L<Fcm::Keyword::Entries|Fcm::Keyword::Entries>,
+L<Fcm::Keyword::Exception|Fcm::Keyword::Exception>,
+L<Fcm::Keyword::Entry::Location|Fcm::Keyword::Entry::Location>
+
+=head1 COPYRIGHT
+
+E<169> Crown copyright Met Office. All rights reserved.
+
+=cut
Index: /OFFICIAL/FCM_V1.5/lib/Fcm/Keyword/Entries.pm
===================================================================
--- /OFFICIAL/FCM_V1.5/lib/Fcm/Keyword/Entries.pm	(revision 12)
+++ /OFFICIAL/FCM_V1.5/lib/Fcm/Keyword/Entries.pm	(revision 12)
@@ -0,0 +1,211 @@
+# ------------------------------------------------------------------------------
+# (C) Crown copyright Met Office. All rights reserved.
+# For further details please refer to the file COPYRIGHT.txt
+# which you should have received as part of this distribution.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+
+package Fcm::Keyword::Entries;
+
+use Carp qw{croak};
+use Fcm::Util::ClassLoader;
+
+sub new {
+    my ($class, $args_ref) = @_;
+    return bless(
+        {
+            entry_class             => 'Fcm::Keyword::Entry',
+            entry_by                => {key => {}, value => {}},
+            has_loaded_entries_from => {},
+            loaders                 => [],
+            ($args_ref && ref($args_ref) eq 'HASH' ? %{$args_ref} : ()),
+        },
+        $class,
+    );
+}
+
+################################################################################
+# Returns the class of entries stored by this entries list
+sub get_entry_class {
+    my ($self) = @_;
+    return $self->{entry_class};
+}
+
+################################################################################
+# Returns all entries
+sub get_all_entries {
+    my ($self) = @_;
+    if (!%{$self->{entry_by}{key}}) {
+        # Nothing set, attempt to load entries
+        $self->load_entries();
+    }
+    if (wantarray()) {
+        return values(%{$self->{entry_by}{key}});
+    }
+    else {
+        return [values(%{$self->{entry_by}{key}})];
+    }
+}
+
+################################################################################
+# Methods: get_entry_by_*
+for my $name (
+    ### Returns an entry with a matching key
+    'key',
+    ### Returns an entry with a matching value
+    'value'
+) {
+    no strict qw{refs};
+    my $method = "get_entry_by_$name";
+    *$method = sub {
+        my ($self, $search_key) = @_;
+        if (!defined($search_key)) {
+            return;
+        }
+        my $sk = ($name eq 'key') ? uc($search_key) : $search_key;
+        if (!exists($self->{entry_by}{$name}{$sk})) {
+            $self->load_entries($name, $sk);
+        }
+        if (exists($self->{entry_by}{$name}{$sk})) {
+            return $self->{entry_by}{$name}{$sk};
+        }
+        else {
+            return;
+        }
+    }
+}
+
+################################################################################
+# Adds an entry
+sub add_entry {
+    my ($self, $key, $value, $args_ref) = @_;
+    Fcm::Util::ClassLoader::load($self->get_entry_class());
+    my $entry = $self->get_entry_class()->new({
+        key   => uc($key),
+        value => $value,
+        ($args_ref && ref($args_ref) eq 'HASH' ? %{$args_ref} : ()),
+    });
+    $self->{entry_by}{key}{uc($key)} = $entry;
+    $self->{entry_by}{value}{$value} = $entry;
+    return $entry;
+}
+
+################################################################################
+# Returns the loaders for this entries list
+sub get_loaders {
+    my ($self) = @_;
+    return (wantarray() ? @{$self->{loaders}} : $self->{loaders});
+}
+
+################################################################################
+# Loads entries using its loaders
+sub load_entries {
+    my ($self, $name, $search_key) = @_;
+    LOADER:
+    for my $loader ($self->get_loaders()) {
+        if ($self->{has_loaded_entries_from}{$loader->get_source()}) {
+            next LOADER;
+        }
+        $self->{has_loaded_entries_from}{$loader->get_source()}
+            = $loader->load_to($self);
+        if ($name && exists($self->{entry_by}{$name}{$search_key})) {
+            last LOADER;
+        }
+    }
+}
+
+1;
+__END__
+
+=head1 NAME
+
+Fcm::Keyword::Entries
+
+=head1 SYNOPSIS
+
+    use Fcm::Keyword::Entries;
+
+    my $entries = Fcm::Keyword::Entries->new({
+        entry_class => $entry_class,
+        loaders     => \@loaders,
+    });
+    $entry = $entries->get_entry_by_key($key);
+    $entry = $entries->get_entry_by_value($value);
+
+    for my $entry ($entries->get_entries()) {
+        # ...
+    }
+
+    $entries->add_entry($key, $value);
+
+=head1 DESCRIPTION
+
+This module is used to manipulate FCM keyword entries. It is used by
+L<Fcm::Keyword|Fcm::Keyword> to store keyword entries, which are
+L<Fcm::Keyword::Entry|Fcm::Keyword::Entry> objects.
+
+=head1 METHODS
+
+=over 4
+
+=item C<new({entry_class =E<gt> $entry_class, loaders =E<gt> \@loaders})>
+
+Constructor. The argument should be a reference to hash, where:
+
+I<entry_class> is a string representing the class name of entries in this
+object. The class must be a sub-class of
+L<Fcm::Keyword::Entry|Fcm::Keyword::Entry>. The default is
+"L<Fcm::Keyword::Entry|Fcm::Keyword::Entry>".
+
+I<loaders> is a reference to an array of
+L<Fcm::Keyword::Loader|Fcm::Keyword::Loader> objects, which will be used to
+load entries into this object. The default is an empty array.
+
+=item add_entry($key,$value)
+
+Adds an entry. Returns the added entry. (Keys are converted to uppercases
+automatically.)
+
+=item get_all_entries()
+
+Returns all entries that are currently loaded.
+
+=item get_entry_by_key($key)
+
+Return an entry, whose key matches $key. (Search is case-insensitive.) Returns
+undef if there is no matching entry.
+
+=item get_entry_by_value($value)
+
+Return an entry, whose value matches $value. (Search is case-sensitive.)
+Returns undef if there is no matching entry.
+
+=item get_loaders()
+
+Returns the loaders for loading entries.
+
+=item load_entries()
+
+Loads entries from its loaders, as returned by get_loaders(). This method can
+also be triggered by get_all_entries(), if the entry list is empty, or by
+get_entry_by_key($key) and get_entry_by_value($value) methods, if there is no
+matching entry in the current lookup lists.
+
+=back
+
+=head1 TO DO
+
+Handle duplicated entries in add_entry($key,$value).
+
+=head1 SEE ALSO
+
+L<Fcm::Keyword|Fcm::Keyword>,
+L<Fcm::Keyword::Entry|Fcm::Keyword::Entry>,
+L<Fcm::Keyword::Loader|Fcm::Keyword::Loader>
+
+=head1 COPYRIGHT
+
+E<169> Crown copyright Met Office. All rights reserved.
+
+=cut
Index: /OFFICIAL/FCM_V1.5/lib/Fcm/Keyword/Entry.pm
===================================================================
--- /OFFICIAL/FCM_V1.5/lib/Fcm/Keyword/Entry.pm	(revision 12)
+++ /OFFICIAL/FCM_V1.5/lib/Fcm/Keyword/Entry.pm	(revision 12)
@@ -0,0 +1,83 @@
+# ------------------------------------------------------------------------------
+# (C) Crown copyright Met Office. All rights reserved.
+# For further details please refer to the file COPYRIGHT.txt
+# which you should have received as part of this distribution.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+
+package Fcm::Keyword::Entry;
+
+sub new {
+    my ($class, $args_ref) = @_;
+    if (!$args_ref) {
+        $args_ref = {};
+    }
+    return bless({%{$args_ref}}, $class);
+}
+
+################################################################################
+### Methods: get_*
+for my $name (
+    # Returns the key of this entry
+    'key',
+    # Returns the value of this entry
+    'value',
+) {
+    no strict qw{refs};
+    my $getter = "get_$name";
+    *$getter = sub {
+        my ($self) = @_;
+        return $self->{$name};
+    }
+}
+
+1;
+__END__
+
+=head1 NAME
+
+Fcm::Keyword::Entry
+
+=head1 SYNOPSIS
+
+    use Fcm::Keyword::Entry;
+
+    $entry = Fcm::Keyword::Entry->new({key => $key, value => $value});
+    $key = $entry->get_key();
+    $value = $entry->get_value();
+
+=head1 DESCRIPTION
+
+An object of this class represents a FCM keyword entry.
+
+=head1 METHODS
+
+=over 4
+
+=item C<new({key =E<gt> $key, value =E<gt> $value})>
+
+Constructor.
+
+=item get_key()
+
+Returns the key of this keyword entry.
+
+=item get_value()
+
+Returns the value of this keyword entry.
+
+=back
+
+Simple formatter for displaying an entry.
+
+=head1 SEE ALSO
+
+L<Fcm::Keyword|Fcm::Keyword>,
+L<Fcm::Keyword::Entries|Fcm::Keyword::Entries>
+
+=head1 COPYRIGHT
+
+E<169> Crown copyright Met Office. All rights reserved.
+
+=cut
Index: /OFFICIAL/FCM_V1.5/lib/Fcm/Keyword/Exception.pm
===================================================================
--- /OFFICIAL/FCM_V1.5/lib/Fcm/Keyword/Exception.pm	(revision 12)
+++ /OFFICIAL/FCM_V1.5/lib/Fcm/Keyword/Exception.pm	(revision 12)
@@ -0,0 +1,42 @@
+# ------------------------------------------------------------------------------
+# (C) Crown copyright Met Office. All rights reserved.
+# For further details please refer to the file COPYRIGHT.txt
+# which you should have received as part of this distribution.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+
+package Fcm::Keyword::Exception;
+use base qw{Fcm::Exception};
+
+1;
+__END__
+
+=head1 NAME
+
+Fcm::Keyword::Exception
+
+=head1 SYNOPSIS
+
+    use Carp qw{croak};
+    use Fcm::Keyword::Exception;
+    croak(Fcm::Keyword::Exception->new({message => 'something is wrong'}));
+
+=head1 DESCRIPTION
+
+This class extends L<Fcm::Exception|Fcm::Exception>. This exception is thrown
+on errors associated with the command line interface.
+
+=head1 METHODS
+
+See L<Fcm::Exception|Fcm::Exception> for a list of methods.
+
+=head1 SEE ALSO
+
+L<Fcm::Exception|Fcm::Exception>
+
+=head1 COPYRIGHT
+
+E<169> Crown copyright Met Office. All rights reserved.
+
+=cut
Index: /OFFICIAL/FCM_V1.5/lib/Fcm/Keyword/Loader.pod
===================================================================
--- /OFFICIAL/FCM_V1.5/lib/Fcm/Keyword/Loader.pod	(revision 12)
+++ /OFFICIAL/FCM_V1.5/lib/Fcm/Keyword/Loader.pod	(revision 12)
@@ -0,0 +1,43 @@
+=head1 NAME
+
+Fcm::Keyword::Loader
+
+=head1 SYNOPSIS
+
+    $loader->load_to($entries);
+
+=head1 DESCRIPTION
+
+This is an interface of a class that loads FCM keywords into a
+L<Fcm::Keyword::Entries|Fcm::Keyword::Entries> object.
+
+=head1 METHODS
+
+=over 4
+
+=item get_source()
+
+The name of the source where this loader loads its FCM keywords from.
+
+=item load_to($entries)
+
+Loads FCM keywords into $entries, which should be a
+L<Fcm::Keyword::Entries|Fcm::Keyword::Entries> object. Returns the number of
+successfully loaded entries.
+
+=back
+
+=head1 SEE ALSO
+
+L<Fcm::Keyword|Fcm::Keyword>,
+L<Fcm::Keyword::Entries|Fcm::Keyword::Entries>,
+L<Fcm::Keyword::Entry|Fcm::Keyword::Entry>,
+L<Fcm::Keyword::Loader::Config::Location|Fcm::Keyword::Loader::Config::Location>,
+L<Fcm::Keyword::Loader::Config::Revision|Fcm::Keyword::Loader::Config::Revision>,
+L<Fcm::Keyword::Loader::VC::Revision|Fcm::Keyword::Loader::VC::Revision>
+
+=head1 COPYRIGHT
+
+E<169> Crown copyright Met Office. All rights reserved.
+
+=cut
Index: /OFFICIAL/FCM_V1.5/lib/Fcm/Util/ClassLoader.pm
===================================================================
--- /OFFICIAL/FCM_V1.5/lib/Fcm/Util/ClassLoader.pm	(revision 12)
+++ /OFFICIAL/FCM_V1.5/lib/Fcm/Util/ClassLoader.pm	(revision 12)
@@ -0,0 +1,80 @@
+# ------------------------------------------------------------------------------
+# (C) Crown copyright Met Office. All rights reserved.
+# For further details please refer to the file COPYRIGHT.txt
+# which you should have received as part of this distribution.
+# ------------------------------------------------------------------------------
+use strict;
+use warnings;
+
+package Fcm::Util::ClassLoader;
+use base qw{Exporter};
+
+our @EXPORT_OK = qw{load};
+
+use Carp qw{croak};
+use Fcm::Exception;
+
+sub load {
+    my ($class, $test_method) = @_;
+    if (!$test_method) {
+        $test_method = 'new';
+    }
+    if (!UNIVERSAL::can($class, $test_method)) {
+        eval('require ' . $class);
+        if ($@) {
+            croak(Fcm::Exception->new({message => sprintf(
+                "%s: class loading failed: %s", $class, $@,
+            )}));
+        }
+    }
+    return $class;
+}
+
+1;
+__END__
+
+=head1 NAME
+
+Fcm::ClassLoader
+
+=head1 SYNOPSIS
+
+    use Fcm::Util::ClassLoader;
+    $load_ok = Fcm::Util::ClassLoader::load($class);
+
+=head1 DESCRIPTION
+
+A wrapper for loading a class dynamically.
+
+=head1 FUNCTIONS
+
+=over 4
+
+=item load($class,$test_method)
+
+If $class can call $test_method, returns $class. Otherwise, attempts to
+require() $class and returns it. If this fails, croak() with a
+L<Fcm::Exception|Fcm::Exception>.
+
+=item load($class)
+
+Shorthand for C<load($class, 'new')>.
+
+=back
+
+=head1 DIAGNOSTICS
+
+=over 4
+
+=item L<Fcm::Exception|Fcm::Exception>
+
+The load($class,$test_method) function croak() with this exception if it fails
+to load the specified class.
+
+=back
+
+=head1 COPYRIGHT
+
+E<169> Crown copyright Met Office. All rights reserved.
+
+=cut
Index: /OFFICIAL/FCM_V1.5/t/Fcm/Build/Fortran-extract-interface-result.f90
===================================================================
--- /OFFICIAL/FCM_V1.5/t/Fcm/Build/Fortran-extract-interface-result.f90	(revision 12)
+++ /OFFICIAL/FCM_V1.5/t/Fcm/Build/Fortran-extract-interface-result.f90	(revision 12)
@@ -0,0 +1,70 @@
+interface
+logical function func_simple()
+end function func_simple
+logical function func_simple_1()
+end function
+logical function func_simple_2()
+end
+pure logical function func_simple_pure()
+end function func_simple_pure
+recursive pure integer function func_simple_recursive_pure(i)
+integer, intent(in) :: i
+end function func_simple_recursive_pure
+elemental logical function func_simple_elemental()
+end function func_simple_elemental
+integer(selected_int_kind(0)) function func_with_use_and_args(egg, ham)
+use foo
+use bar, only:&
+ & i_am_dim
+integer, intent(in) :: egg(i_am_dim)
+integer, intent(in) :: ham(i_am_dim, 2)
+end function func_with_use_and_args
+character(20) function func_with_parameters(egg, ham)
+character*(*), parameter :: x_param = '01234567890'
+character(*), parameter :: &
+ y_param &
+ = '!&!&!&!&!&!'
+character(len(x_param)), intent(in) :: egg
+character(len(y_param)), intent(in) :: ham
+end function func_with_parameters
+function func_with_parameters_1(egg, ham) result(r)
+integer, parameter :: x_param = 10
+integer z_param
+parameter(z_param = 2)
+real, intent(in), dimension(x_param) :: egg
+integer, intent(in) :: ham
+logical :: r(z_param)
+end function func_with_parameters_1
+character(10) function func_with_contains(mushroom, tomoato)
+character(5) mushroom
+character(5) tomoato
+end function func_with_contains
+Function func_mix_local_and_result(egg, ham, bacon) Result(Breakfast)
+Integer, Intent(in) :: egg, ham
+Real, Intent(in) :: bacon
+Real :: tomato, breakfast
+End Function func_mix_local_and_result
+subroutine sub_simple()
+end subroutine sub_simple
+subroutine sub_simple_1()
+end subroutine
+subroutine sub_simple_2()
+end
+subroutine sub_simple_3()
+end sub&
+&routine&
+& sub_simple_3
+subroutine sub_with_contains(foo)
+character*(len('!"&''&"!')) &
+ foo
+end subroutine sub_with_contains
+subroutine sub_with_renamed_import(i_am_dim)
+integer, parameter :: d = 2
+complex :: i_am_dim(d)
+end subroutine sub_with_renamed_import
+subroutine sub_with_external(proc)
+external proc
+end subroutine sub_with_external
+subroutine sub_with_end()
+end subroutine sub_with_end
+end interface
Index: /OFFICIAL/FCM_V1.5/t/Fcm/Build/Fortran-extract-interface-source.f90
===================================================================
--- /OFFICIAL/FCM_V1.5/t/Fcm/Build/Fortran-extract-interface-source.f90	(revision 12)
+++ /OFFICIAL/FCM_V1.5/t/Fcm/Build/Fortran-extract-interface-source.f90	(revision 12)
@@ -0,0 +1,181 @@
+! A simple function
+logical function func_simple()
+func_simple = .true.
+end function func_simple
+
+! A simple function, but with less friendly end
+logical function func_simple_1()
+func_simple_1 = .true.
+end function
+
+! A simple function, but with even less friendly end
+logical function func_simple_2()
+func_simple_2 = .true.
+end
+
+! A pure simple function
+pure logical function func_simple_pure()
+func_simple_pure = .true.
+end function func_simple_pure
+
+! A pure recursive function
+recursive pure integer function func_simple_recursive_pure(i)
+integer, intent(in) :: i
+if (i <= 0) then
+    func_simple_recursive_pure = i
+else
+    func_simple_recursive_pure = i + func_simple_recursive_pure(i - 1)
+end if
+end function func_simple_recursive_pure
+
+! An elemental simple function
+elemental logical function func_simple_elemental()
+func_simple_elemental = .true.
+end function func_simple_elemental
+
+! A module with nonsense
+module bar
+type food
+integer :: cooking_method
+end type food
+type organic
+integer :: growing_method
+end type organic
+integer, parameter :: i_am_dim = 10
+end module bar
+
+! A module with more nonsense
+module foo
+use bar, only: FOOD
+integer :: foo_int
+contains
+subroutine foo_sub(egg)
+integer, parameter :: egg_dim = 10
+type(Food), intent(in) :: egg
+write(*, *) egg
+end subroutine foo_sub
+elemental function foo_func() result(f)
+integer :: f
+f = 0
+end function
+end module foo
+
+! An function with arguments and module imports
+integer(selected_int_kind(0)) function func_with_use_and_args(egg, ham)
+use foo
+! Deliberate trailing spaces in next line
+use bar, only : organic,     i_am_dim   
+implicit none
+integer, intent(in) :: egg(i_am_dim)
+integer, intent(in) :: ham(i_am_dim, 2)
+real bacon
+! Deliberate trailing spaces in next line
+type(   organic   ) :: tomato   
+func_with_use_and_args = egg(1) + ham(1, 1)
+end function func_with_use_and_args
+
+! A function with some parameters
+character(20) function func_with_parameters(egg, ham)
+implicit none
+character*(*), parameter :: x_param = '01234567890'
+character(*), parameter :: & ! throw in some comments
+    y_param                &
+    = '!&!&!&!&!&!'          ! how to make life interesting
+integer, parameter :: z = 20
+character(len(x_param)), intent(in) :: egg
+character(len(y_param)), intent(in) :: ham
+func_with_parameters = egg // ham
+end function func_with_parameters
+
+! A function with some parameters, with a result
+function func_with_parameters_1(egg, ham) result(r)
+implicit none
+integer, parameter :: x_param = 10
+integer z_param
+parameter(z_param = 2)
+real, intent(in), dimension(x_param) :: egg
+integer, intent(in) :: ham
+logical :: r(z_param)
+r(1) = int(egg(1)) + ham > 0
+r(2) = .false.
+end function func_with_parameters_1
+
+! A function with a contains
+character(10) function func_with_contains(mushroom, tomoato)
+character(5) mushroom
+character(5) tomoato
+func_with_contains = func_with_contains_1()
+contains
+character(10) function func_with_contains_1()
+func_with_contains_1 = mushroom // tomoato
+end function func_with_contains_1
+end function func_with_contains
+
+! A function with its result declared after a local in the same statement
+Function func_mix_local_and_result(egg, ham, bacon) Result(Breakfast)
+Integer, Intent(in) :: egg, ham
+Real, Intent(in) :: bacon
+Real :: tomato, breakfast
+Breakfast = real(egg) + real(ham) + bacon
+End Function func_mix_local_and_result
+
+! A simple subroutine
+subroutine sub_simple()
+end subroutine sub_simple
+
+! A simple subroutine, with not so friendly end
+subroutine sub_simple_1()
+end subroutine
+
+! A simple subroutine, with even less friendly end
+subroutine sub_simple_2()
+end
+
+! A simple subroutine, with funny continuation
+subroutine sub_simple_3()
+end sub&
+&routine&
+& sub_simple_3
+
+! A subroutine with a few contains
+subroutine sub_with_contains(foo) ! " &
+! Deliberate trailing spaces in next line
+use Bar, only: i_am_dim    
+character*(len('!"&''&"!')) & ! what a mess!
+    foo
+call sub_with_contains_first()
+call sub_with_contains_second()
+call sub_with_contains_third()
+print*, foo
+contains
+subroutine sub_with_contains_first()
+interface
+integer function x()
+end function x
+end interface
+end subroutine sub_with_contains_first
+subroutine sub_with_contains_second()
+end subroutine
+subroutine sub_with_contains_third()
+end subroutine
+end subroutine sub_with_contains
+
+! A subroutine with a renamed module import
+subroutine sub_with_renamed_import(i_am_dim)
+use bar, only: i_am_not_dim => i_am_dim
+integer, parameter :: d = 2
+complex :: i_am_dim(d)
+print*, i_am_dim
+end subroutine sub_with_renamed_import
+
+! A subroutine with an external argument
+subroutine sub_with_external(proc)
+external proc
+call proc()
+end subroutine sub_with_external
+
+! A subroutine with a variable named "end"
+subroutine sub_with_end()
+integer :: end
+end = 0
+end subroutine sub_with_end
Index: /OFFICIAL/FCM_V1.5/t/Fcm/Build/Fortran.t
===================================================================
--- /OFFICIAL/FCM_V1.5/t/Fcm/Build/Fortran.t	(revision 12)
+++ /OFFICIAL/FCM_V1.5/t/Fcm/Build/Fortran.t	(revision 12)
@@ -0,0 +1,43 @@
+#!/usr/bin/perl
+# ------------------------------------------------------------------------------
+# (C) Crown copyright Met Office. All rights reserved.
+# For further details please refer to the file COPYRIGHT.txt
+# which you should have received as part of this distribution.
+# ------------------------------------------------------------------------------
+
+use strict;
+use warnings;
+
+use FindBin;
+use lib "$FindBin::Bin/../../../lib";
+
+use Test::More (tests => 3);
+
+if (!caller()) {
+    main(@ARGV);
+}
+
+sub main {
+    my $CLASS = 'Fcm::Build::Fortran';
+    use_ok($CLASS);
+    my $util = $CLASS->new();
+    isa_ok($util, $CLASS);
+    test_extract_interface($util);
+}
+
+sub test_extract_interface {
+    my ($util) = @_;
+    my $root = ($0 =~ qr{\A(.+)\.t\z}msx)[0];
+    my $f90 = $root . '-extract-interface-source.f90';
+    my $f90_interface = $root . '-extract-interface-result.f90';
+    open(my($handle_for_source), '<', $f90) || die("$f90: $!");
+    my @actual_lines = $util->extract_interface($handle_for_source);
+    close($handle_for_source);
+    open(my($handle_for_result), '<', $f90_interface)
+        || die("$f90_interface: $!");
+    my @expected_lines = readline($handle_for_result);
+    close($handle_for_result);
+    is_deeply(\@actual_lines, \@expected_lines, 'extract_interface');
+}
+
+__END__
Index: /OFFICIAL/FCM_V1.5/t/Fcm/CLI/Config.t
===================================================================
--- /OFFICIAL/FCM_V1.5/t/Fcm/CLI/Config.t	(revision 12)
+++ /OFFICIAL/FCM_V1.5/t/Fcm/CLI/Config.t	(revision 12)
@@ -0,0 +1,86 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Fcm::CLI::Config::Default;
+use Fcm::CLI::Subcommand;
+use Test::More qw{no_plan};
+
+main();
+
+sub main {
+    my $class = 'Fcm::CLI::Config';
+    use_ok($class);
+    test_get_instance($class);
+    test_get_subcommand_of_string($class);
+}
+
+################################################################################
+# Tests normal usage of getting an instance
+sub test_get_instance {
+    my ($class) = @_;
+    my $prefix = 'constructor';
+    my $cli_config = $class->instance();
+    isa_ok($cli_config, $class, "$prefix");
+    is_deeply(
+        [$cli_config->get_core_subcommands()],
+        \@Fcm::CLI::Config::Default::CORE_SUBCOMMANDS,
+        "$prefix: default core",
+    );
+    is_deeply(
+        [$cli_config->get_vc_subcommands()],
+        \@Fcm::CLI::Config::Default::VC_SUBCOMMANDS,
+        "$prefix: default vc",
+    );
+    is_deeply(
+        [$cli_config->get_subcommands()],
+        [$cli_config->get_core_subcommands(), $cli_config->get_vc_subcommands()],
+        "$prefix: default",
+    );
+    is($class->instance(), $cli_config, "$prefix: same instance");
+    isnt($class->instance({}), $cli_config, "$prefix: not the same instance");
+    my $empty_cli_config = $class->instance({
+        core_subcommands => [],
+        vc_subcommands   => [],
+    });
+    is_deeply(
+        [$empty_cli_config->get_core_subcommands()],
+        [],
+        "$prefix: empty core",
+    );
+    is_deeply(
+        [$empty_cli_config->get_vc_subcommands()],
+        [],
+        "$prefix: empty vc",
+    );
+    is_deeply(
+        [$empty_cli_config->get_subcommands()],
+        [],
+        "$prefix: empty",
+    );
+}
+
+################################################################################
+# Tests getting a subcommand of a matching string
+sub test_get_subcommand_of_string {
+    my ($class) = @_;
+    my $prefix = 'get_subcommand_of';
+    my $foo_subcommand = Fcm::CLI::Subcommand->new({names => ['food', 'foo']});
+    my $bar_subcommand = Fcm::CLI::Subcommand->new({names => ['barley', 'bar']});
+    my $cli_config = $class->instance({
+        core_subcommands => [$foo_subcommand, $bar_subcommand],
+        vc_subcommands   => [],
+    });
+    for my $key ('food', 'foo') {
+        is($cli_config->get_subcommand_of($key), $foo_subcommand,
+            "$prefix: $key");
+    }
+    for my $key ('barley', 'bar') {
+        is($cli_config->get_subcommand_of($key), $bar_subcommand,
+            "$prefix: $key");
+    }
+    is($cli_config->get_subcommand_of('baz'), undef, "$prefix: baz");
+}
+
+__END__
Index: /OFFICIAL/FCM_V1.5/t/Fcm/CLI/Exception.t
===================================================================
--- /OFFICIAL/FCM_V1.5/t/Fcm/CLI/Exception.t	(revision 12)
+++ /OFFICIAL/FCM_V1.5/t/Fcm/CLI/Exception.t	(revision 12)
@@ -0,0 +1,27 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More qw{no_plan};
+
+main();
+
+sub main {
+    my $class = 'Fcm::CLI::Exception';
+    use_ok($class);
+    test_normal($class);
+}
+
+################################################################################
+# Tests normal usage
+sub test_normal {
+    my ($class) = @_;
+    my $prefix = 'normal';
+    my $e = $class->new({message => 'message'});
+    isa_ok($e, $class, $prefix);
+    is("$e", "$class: message\n", "$prefix: as_string()");
+    is($e->get_message(), 'message', "$prefix: get_message()");
+}
+
+__END__
Index: /OFFICIAL/FCM_V1.5/t/Fcm/CLI/Invoker.t
===================================================================
--- /OFFICIAL/FCM_V1.5/t/Fcm/CLI/Invoker.t	(revision 12)
+++ /OFFICIAL/FCM_V1.5/t/Fcm/CLI/Invoker.t	(revision 12)
@@ -0,0 +1,38 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More qw{no_plan};
+
+main();
+
+sub main {
+    my $class = 'Fcm::CLI::Invoker';
+    use_ok($class);
+    test_normal($class);
+}
+
+################################################################################
+# Tests normal usage
+sub test_normal {
+    my ($class) = @_;
+    my $prefix = "normal";
+    my %OPTIONS   = (option1 => 1, option2 => 2, option3 => 3);
+    my @ARGUMENTS = ('argument 1', 'argument 2');
+    my $invoker = $class->new({
+        command   => 'command',
+        options   => \%OPTIONS,
+        arguments => \@ARGUMENTS,
+    });
+    isa_ok($invoker, $class, $prefix);
+    is($invoker->get_command(), 'command', "$prefix: command");
+    is_deeply({$invoker->get_options()}, \%OPTIONS, "$prefix: options");
+    is_deeply([$invoker->get_arguments()], \@ARGUMENTS, "$prefix: arguments");
+    eval {
+        $invoker->invoke();
+    };
+    isa_ok($@, 'Fcm::CLI::Exception', "$prefix: invoke");
+}
+
+__END__
Index: /OFFICIAL/FCM_V1.5/t/Fcm/CLI/Option.t
===================================================================
--- /OFFICIAL/FCM_V1.5/t/Fcm/CLI/Option.t	(revision 12)
+++ /OFFICIAL/FCM_V1.5/t/Fcm/CLI/Option.t	(revision 12)
@@ -0,0 +1,168 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More qw{no_plan};
+
+main();
+
+sub main {
+    my $class = 'Fcm::CLI::Option';
+    use_ok($class);
+    test_simplest($class);
+    test_simplest_scalar_arg($class);
+    test_simplest_array_arg($class);
+    test_simplest_hash_arg($class);
+    test_simple($class);
+    test_simple_scalar_arg($class);
+    test_simple_array_arg($class);
+    test_simple_hash_arg($class);
+    test_long_letter($class);
+}
+
+################################################################################
+# Tests simplest usage
+sub test_simplest {
+    my ($class) = @_;
+    my $prefix = 'simplest';
+    my $option = $class->new({
+        delimiter   => 'delimiter-value',
+        description => 'description value',
+        name        => 'name-value',
+    });
+    isa_ok($option, $class);
+    is($option->get_delimiter(), 'delimiter-value', "$prefix: delimiter");
+    is($option->get_description(), 'description value', "$prefix: description");
+    is($option->get_name(), 'name-value', "$prefix: name");
+    is($option->get_letter(), undef, "$prefix: letter");
+    ok(!$option->has_arg(), "$prefix: has arg");
+    is($option->get_arg_for_getopt_long(), 'name-value', "$prefix: has arg");
+}
+
+################################################################################
+# Tests simplest usage with a scalar argument
+sub test_simplest_scalar_arg {
+    my ($class) = @_;
+    my $prefix = 'simplest scalar arg';
+    my $option = $class->new({
+        description => 'description value',
+        name        => 'name-value',
+        has_arg     => $class->SCALAR_ARG,
+    });
+    isa_ok($option, $class);
+    is($option->has_arg(), $class->SCALAR_ARG, "$prefix: has arg");
+    is($option->get_arg_for_getopt_long(), 'name-value=s', "$prefix: has arg");
+}
+
+################################################################################
+# Tests simplest usage with array argument
+sub test_simplest_array_arg {
+    my ($class) = @_;
+    my $prefix = 'simplest array arg';
+    my $option = $class->new({
+        description => 'description value',
+        name        => 'name-value',
+        has_arg     => $class->ARRAY_ARG,
+    });
+    isa_ok($option, $class);
+    is($option->has_arg(), $class->ARRAY_ARG, "$prefix: has arg");
+    is($option->get_arg_for_getopt_long(), 'name-value=s@', "$prefix: has arg");
+}
+
+################################################################################
+# Tests simplest usage with hash argument
+sub test_simplest_hash_arg {
+    my ($class) = @_;
+    my $prefix = 'simplest hash arg';
+    my $option = $class->new({
+        description => 'description value',
+        name        => 'name-value',
+        has_arg     => $class->HASH_ARG,
+    });
+    isa_ok($option, $class);
+    is($option->has_arg(), $class->HASH_ARG, "$prefix: has arg");
+    is($option->get_arg_for_getopt_long(), 'name-value=s%', "$prefix: has arg");
+}
+
+################################################################################
+# Tests simple usage
+sub test_simple {
+    my ($class) = @_;
+    my $prefix = 'simple';
+    my $option = $class->new({
+        description => 'description value',
+        name        => 'name-value',
+        letter      => 'n',
+    });
+    isa_ok($option, $class);
+    is($option->get_description(), 'description value', "$prefix: description");
+    is($option->get_name(), 'name-value', "$prefix: name");
+    is($option->get_letter(), 'n', "$prefix: letter");
+    is($option->has_arg(), $class->NO_ARG, "$prefix: has arg");
+    is($option->get_arg_for_getopt_long(), 'name-value|n', "$prefix: has arg");
+}
+
+################################################################################
+# Tests simple usage with a scalar argument
+sub test_simple_scalar_arg {
+    my ($class) = @_;
+    my $prefix = 'simple scalar arg';
+    my $option = $class->new({
+        description => 'description value',
+        name        => 'name-value',
+        letter      => 'n',
+        has_arg     => $class->SCALAR_ARG,
+    });
+    isa_ok($option, $class);
+    is($option->has_arg(), $class->SCALAR_ARG, "$prefix: has arg");
+    is($option->get_arg_for_getopt_long(), 'name-value|n=s', "$prefix: has arg");
+}
+
+################################################################################
+# Tests simplest usage with array argument
+sub test_simple_array_arg {
+    my ($class) = @_;
+    my $prefix = 'simple array arg';
+    my $option = $class->new({
+        description => 'description value',
+        name        => 'name-value',
+        letter      => 'n',
+        has_arg     => $class->ARRAY_ARG,
+    });
+    isa_ok($option, $class);
+    is($option->has_arg(), $class->ARRAY_ARG, "$prefix: has arg");
+    is($option->get_arg_for_getopt_long(), 'name-value|n=s@', "$prefix: has arg");
+}
+
+################################################################################
+# Tests simplest usage with hash argument
+sub test_simple_hash_arg {
+    my ($class) = @_;
+    my $prefix = 'simple hash arg';
+    my $option = $class->new({
+        description => 'description value',
+        name        => 'name-value',
+        letter      => 'n',
+        has_arg     => $class->HASH_ARG,
+    });
+    isa_ok($option, $class);
+    is($option->has_arg(), $class->HASH_ARG, "$prefix: has arg");
+    is($option->get_arg_for_getopt_long(), 'name-value|n=s%', "$prefix: has arg");
+}
+
+################################################################################
+# Tests longer than 1 letter
+sub test_long_letter {
+    my ($class) = @_;
+    my $prefix = 'long letter';
+    my $option = $class->new({
+        name   => 'name-value',
+        letter => 'name',
+    });
+    isa_ok($option, $class);
+    is($option->get_letter(), 'n', "$prefix: letter");
+    is($option->get_arg_for_getopt_long(), 'name-value|n', "$prefix: has arg");
+}
+
+__END__
Index: /OFFICIAL/FCM_V1.5/t/Fcm/CLI/Subcommand.t
===================================================================
--- /OFFICIAL/FCM_V1.5/t/Fcm/CLI/Subcommand.t	(revision 12)
+++ /OFFICIAL/FCM_V1.5/t/Fcm/CLI/Subcommand.t	(revision 12)
@@ -0,0 +1,74 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Fcm::CLI::Option;
+use Test::More qw{no_plan};
+
+main();
+
+sub main {
+    my $class = 'Fcm::CLI::Subcommand';
+    use_ok($class);
+    test_constructor($class);
+    test_has_a_name($class);
+    test_as_string($class);
+}
+
+################################################################################
+# Tests the constructor
+sub test_constructor {
+    my ($class) = @_;
+    my $prefix = 'constructor';
+    my %OPTION_OF = (
+        description    => 'description value',
+        invoker_class  => 'invoker_class value',
+        invoker_config => 'invoker_config value',
+        is_vc          => 'is_vc value',
+        names          => 'names value',
+        options        => 'options value',
+        synopsis       => 'synopsis value',
+        usage          => 'usage value',
+    );
+    my $subcommand = Fcm::CLI::Subcommand->new(\%OPTION_OF);
+    isa_ok($subcommand, $class, $prefix);
+    for my $key (keys(%OPTION_OF)) {
+        my $getter = index($key, 'is') == 0 ? $key : "get_$key";
+        is($subcommand->$getter(), $OPTION_OF{$key}, "$prefix: $getter");
+    }
+}
+
+################################################################################
+# Tests match a string name to a subcommand
+sub test_has_a_name {
+    my ($class) = @_;
+    my $prefix = 'has a name';
+    my @NAMES = ('foo', 'bar', 'baz');
+    my $subcommand = $class->new({names => \@NAMES});
+    for my $name (@NAMES) {
+        ok($subcommand->has_a_name($name), "$prefix: $name");
+    }
+    for my $name (qw{egg ham mayo}) {
+        ok(!$subcommand->has_a_name($name), "$prefix: $name");
+    }
+}
+
+################################################################################
+# Tests string representation of a subcommand
+sub test_as_string {
+    my ($class) = @_;
+    my $prefix = 'as string';
+    my %OPTION_OF = (
+        'foo (bar, baz)' => ['foo', 'bar', 'baz'],
+        'foo (bar)'      => ['foo', 'bar'],
+        'foo'            => ['foo'],
+        q{}              => [],
+    );
+    for my $key (keys(%OPTION_OF)) {
+        my $subcommand = $class->new({names => $OPTION_OF{$key}});
+        is($subcommand->as_string(), $key, "$prefix: $key");
+    }
+}
+
+__END__
Index: /OFFICIAL/FCM_V1.5/t/Fcm/Interactive/InputGetter.t
===================================================================
--- /OFFICIAL/FCM_V1.5/t/Fcm/Interactive/InputGetter.t	(revision 12)
+++ /OFFICIAL/FCM_V1.5/t/Fcm/Interactive/InputGetter.t	(revision 12)
@@ -0,0 +1,34 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More qw{no_plan};
+
+main();
+
+sub main {
+    my $class = 'Fcm::Interactive::InputGetter';
+    use_ok($class);
+    test_constructor($class);
+}
+
+################################################################################
+# Tests usage of constructor
+sub test_constructor {
+    my ($class) = @_;
+    my $prefix = 'constructor';
+    my $input_getter = $class->new({
+        title   => 'title-value',
+        message => 'message-value',
+        type    => 'type-value',
+        default => 'default-value',
+    });
+    isa_ok($input_getter, $class);
+    is($input_getter->get_title(), 'title-value', "$prefix: get title");
+    is($input_getter->get_message(), 'message-value', "$prefix: get message");
+    is($input_getter->get_type(), 'type-value', "$prefix: get type");
+    is($input_getter->get_default(), 'default-value', "$prefix: get default");
+}
+
+__END__
Index: /OFFICIAL/FCM_V1.5/t/Fcm/Keyword/Config.t
===================================================================
--- /OFFICIAL/FCM_V1.5/t/Fcm/Keyword/Config.t	(revision 12)
+++ /OFFICIAL/FCM_V1.5/t/Fcm/Keyword/Config.t	(revision 12)
@@ -0,0 +1,15 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More qw{no_plan};
+
+main();
+
+sub main {
+    my $module = 'Fcm::Keyword::Config';
+    use_ok($module);
+}
+
+__END__
Index: /OFFICIAL/FCM_V1.5/t/Fcm/Keyword/Entries.t
===================================================================
--- /OFFICIAL/FCM_V1.5/t/Fcm/Keyword/Entries.t	(revision 12)
+++ /OFFICIAL/FCM_V1.5/t/Fcm/Keyword/Entries.t	(revision 12)
@@ -0,0 +1,228 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+################################################################################
+# A Fcm::Keyword::Entry sub-class for testing
+{
+    package TestEntry;
+    use base qw{Fcm::Keyword::Entry};
+}
+
+################################################################################
+# A mock loader implementing the Fcm::Keyword::Loader interface
+{
+    package MockLoader0;
+    use Scalar::Util qw{blessed};
+
+    ############################################################################
+    # Constructor
+    sub new {
+        my ($class) = @_;
+        return bless({number_of_calls_to_load_to => 0}, $class);
+    }
+
+    ############################################################################
+    ##Returns the package name
+    sub get_source {
+        my ($self) = @_;
+        return blessed($self);
+    }
+
+    ############################################################################
+    # Returns number of times $self->load_to() has been called
+    sub get_number_of_calls_to_load_to {
+        my ($self) = @_;
+        return $self->{number_of_calls_to_load_to};
+    }
+
+    ############################################################################
+    # Loads data into $entries, and returns number of entries loaded
+    sub load_to {
+        my ($self, $entries) = @_;
+        $self->{number_of_calls_to_load_to}++;
+        return $self->load_to_impl($entries);
+    }
+
+    ############################################################################
+    # Returns 0
+    sub load_to_impl {
+        my ($self, $entries) = @_;
+        return 0;
+    }
+}
+
+################################################################################
+# A mock loader implementing the Fcm::Keyword::Loader interface
+{
+    package MockLoader1;
+    our @ISA = qw{MockLoader0};
+
+    my %VALUE_OF = (foo => 'foo1', bar => 'bar2', baz => 'baz3');
+
+    ############################################################################
+    # Returns a reference to the mock data
+    sub get_data {
+        my ($class) = @_;
+        return \%VALUE_OF;
+    }
+
+    ############################################################################
+    ##Writes mock data to the $entries object
+    sub load_to_impl {
+        my ($self, $entries) = @_;
+        my $counter = 0;
+        for my $key (keys(%{$self->get_data()})) {
+            $entries->add_entry($key, $self->get_data()->{$key});
+            $counter++;
+        }
+        return $counter;
+    }
+}
+
+################################################################################
+# A mock loader implementing the Fcm::Keyword::Loader interface
+{
+    package MockLoader2;
+    our @ISA = qw{MockLoader1};
+
+    my %VALUE_OF = (sausages => 'pig', eggs => 'hen', chips => 'potato');
+
+    ############################################################################
+    # Returns a reference to the mock data
+    sub get_data {
+        my ($class) = @_;
+        return \%VALUE_OF;
+    }
+}
+
+package main;
+
+use Test::More qw{no_plan};
+
+main();
+
+sub main {
+    my $class = 'Fcm::Keyword::Entries';
+    use_ok($class);
+    test_empty_constructor($class);
+    test_constructor($class);
+    test_add_entry($class);
+    test_loaders($class);
+}
+
+################################################################################
+# Tests empty constructor
+sub test_empty_constructor {
+    my ($class) = @_;
+    my $prefix = 'empty constructor';
+    my $entries = $class->new();
+    isa_ok($entries, $class);
+    is($entries->get_entry_class(), 'Fcm::Keyword::Entry',
+        "$prefix: default entry class");
+    is_deeply([$entries->get_loaders()], [], "$prefix: empty list of loaders");
+    is_deeply([$entries->get_all_entries()], [],
+        "$prefix: empty list of entries");
+    for my $arg ('foo', undef) {
+        is($entries->get_entry_by_key($arg), undef,
+            "$prefix: entry by key: undef");
+        is($entries->get_entry_by_value($arg), undef,
+            "$prefix: entry by value: undef");
+    }
+}
+
+################################################################################
+# Tests other constructor usages
+sub test_constructor {
+    my ($class) = @_;
+    my $prefix = 'constructor';
+    my @loaders = (MockLoader1->new(), MockLoader2->new());
+    my $entries = $class->new({
+        entry_class => 'not-a-class',
+        loaders     => \@loaders,
+    });
+    isa_ok($entries, $class);
+    is($entries->get_entry_class(), 'not-a-class', "$prefix: entry class");
+    is_deeply([$entries->get_loaders()], \@loaders, "$prefix: list of loaders");
+    eval {
+        $entries->add_entry('key', 'value');
+    };
+    isnt($@, undef, "$prefix: invalid entry class");
+}
+
+################################################################################
+# Tests adding entries
+sub test_add_entry {
+    my ($class) = @_;
+    my $prefix = 'add entry';
+    my %VALUE_OF = (key1 => 'value1', egg => 'white and yolk', 'xyz.abc' => '');
+    for my $entry_class ('Fcm::Keyword::Entry', 'TestEntry') {
+        my $entries = $class->new({entry_class => $entry_class});
+        my $number_of_entries = 0;
+        for my $key (keys(%VALUE_OF)) {
+            my $entry = $entries->add_entry($key, $VALUE_OF{$key});
+            isa_ok($entry, $entry_class);
+            is(scalar(@{$entries->get_all_entries()}), ++$number_of_entries,
+                "$prefix: number of entries: $number_of_entries");
+        }
+        for my $key (keys(%VALUE_OF)) {
+            my $entry = $entries->get_entry_by_key($key);
+            isa_ok($entry, $entry_class);
+            is($entry->get_key(), uc($key), "$prefix: get by key: $key");
+            is($entry->get_value(), $VALUE_OF{$key},
+                "$prefix: get by key: $key: value");
+        }
+        for my $key (keys(%VALUE_OF)) {
+            my $entry = $entries->get_entry_by_value($VALUE_OF{$key});
+            isa_ok($entry, $entry_class);
+            is($entry->get_key(), uc($key), "$prefix: get by value: $key");
+            is($entry->get_value(), $VALUE_OF{$key},
+                "$prefix: get by value: $key: value");
+        }
+        is($entries->get_entry_by_key('no-such-key'), undef,
+            "$prefix: get by key: no-such-key");
+        is($entries->get_entry_by_value('no-such-value'), undef,
+            "$prefix: get by value: no-such-value");
+    }
+}
+
+################################################################################
+# Tests usage of loaders
+sub test_loaders {
+    my ($class) = @_;
+    my $prefix = "loader";
+    my @loaders = (MockLoader0->new(), MockLoader1->new(), MockLoader2->new());
+    my $entries = $class->new({loaders => \@loaders});
+    for my $loader (@loaders) {
+        is($loader->get_number_of_calls_to_load_to(), 0, "$prefix: not loaded");
+    }
+    for my $key (keys(%{$loaders[1]->get_data()})) {
+        my $value = $loaders[1]->get_data()->{$key};
+        my $entry = $entries->get_entry_by_key($key);
+        is($entry->get_key(), uc($key), "$prefix: by key: $key: key");
+        is($entries->get_entry_by_value($value), $entry,
+            "$prefix: by value: $key: object");
+    }
+    is($loaders[0]->get_number_of_calls_to_load_to(), 1,
+        "$prefix: loaded once: 0");
+    is($loaders[1]->get_number_of_calls_to_load_to(), 1,
+        "$prefix: loaded once: 1");
+    is($loaders[2]->get_number_of_calls_to_load_to(), 0,
+        "$prefix: not loaded: 2");
+    for my $key (keys(%{$loaders[2]->get_data()})) {
+        my $value = $loaders[2]->get_data()->{$key};
+        my $entry = $entries->get_entry_by_key($key);
+        is($entry->get_key(), uc($key), "$prefix: by key: $key: key");
+        is($entries->get_entry_by_value($value), $entry,
+            "$prefix: by value: $key: object");
+    }
+    is($loaders[0]->get_number_of_calls_to_load_to(), 2,
+        "$prefix: loaded once: 0");
+    is($loaders[1]->get_number_of_calls_to_load_to(), 1,
+        "$prefix: loaded once: 1");
+    is($loaders[2]->get_number_of_calls_to_load_to(), 1,
+        "$prefix: not loaded: 2");
+}
+
+__END__
Index: /OFFICIAL/FCM_V1.5/t/Fcm/Keyword/Entry.t
===================================================================
--- /OFFICIAL/FCM_V1.5/t/Fcm/Keyword/Entry.t	(revision 12)
+++ /OFFICIAL/FCM_V1.5/t/Fcm/Keyword/Entry.t	(revision 12)
@@ -0,0 +1,26 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More qw{no_plan};
+
+main();
+
+sub main {
+    my $class = 'Fcm::Keyword::Entry';
+    use_ok($class);
+    test_normal($class);
+}
+
+################################################################################
+# Tests normal usage
+sub test_normal {
+    my ($class) = 'Fcm::Keyword::Entry';
+    my $entry = $class->new({key => 'key', value => 'value'});
+    isa_ok($entry, $class);
+    is($entry->get_key(), 'key', "normal: get_key()");
+    is($entry->get_value(), 'value', "normal: get_value()");
+}
+
+__END__
Index: /OFFICIAL/FCM_V1.5/t/Fcm/Keyword/Exception.t
===================================================================
--- /OFFICIAL/FCM_V1.5/t/Fcm/Keyword/Exception.t	(revision 12)
+++ /OFFICIAL/FCM_V1.5/t/Fcm/Keyword/Exception.t	(revision 12)
@@ -0,0 +1,27 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More qw{no_plan};
+
+main();
+
+sub main {
+    my $class = 'Fcm::Keyword::Exception';
+    use_ok($class);
+    test_normal($class);
+}
+
+################################################################################
+# Tests normal usage
+sub test_normal {
+    my ($class) = @_;
+    my $prefix = 'normal';
+    my $e = $class->new({message => 'message'});
+    isa_ok($e, $class, $prefix);
+    is("$e", "$class: message\n", "$prefix: as_string()");
+    is($e->get_message(), 'message', "$prefix: get_message()");
+}
+
+__END__
Index: /OFFICIAL/FCM_V1.5/t/Fcm/Util/ClassLoader.t
===================================================================
--- /OFFICIAL/FCM_V1.5/t/Fcm/Util/ClassLoader.t	(revision 12)
+++ /OFFICIAL/FCM_V1.5/t/Fcm/Util/ClassLoader.t	(revision 12)
@@ -0,0 +1,55 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+################################################################################
+# A class for testing the loader
+{
+    package MyTestClass;
+
+    sub new {
+        my ($class) = @_;
+        return bless(\do{my $annon_scalar}, $class);
+    }
+}
+
+use Test::More qw{no_plan};
+
+main();
+
+sub main {
+    use_ok('Fcm::Util::ClassLoader');
+    test_normal();
+    test_bad();
+}
+
+################################################################################
+# Tests loading classes that should load OK
+sub test_normal {
+    my $prefix = 'normal';
+    my @CLASSES = (
+        'Fcm::CLI::Config',
+        'Fcm::Exception',
+        'Fcm::CLI::Config', # repeat
+        'MyTestClass',
+    );
+    for my $class (@CLASSES) {
+        ok(Fcm::Util::ClassLoader::load($class), "$prefix: load $class");
+    }
+}
+
+################################################################################
+# Tests loading classes that should fail
+sub test_bad {
+    my $prefix = 'bad';
+    my @CLASSES = ('Foo', 'Bar', 'Baz', 'No::Such::Class', 'Foo');
+    for my $class (@CLASSES) {
+        eval {
+            Fcm::Util::ClassLoader::load($class);
+        };
+        isa_ok($@, 'Fcm::Exception', "$prefix: load $class");
+    }
+}
+
+__END__
Index: /OFFICIAL/FCM_V1.5/test/repos/add_subroutine/hello.F90
===================================================================
--- /OFFICIAL/FCM_V1.5/test/repos/add_subroutine/hello.F90	(revision 12)
+++ /OFFICIAL/FCM_V1.5/test/repos/add_subroutine/hello.F90	(revision 12)
@@ -0,0 +1,20 @@
+PROGRAM Hello
+
+USE Hello_Constants, ONLY: hello_string
+
+IMPLICIT NONE
+
+#if defined(CALL_HELLO_SUB)
+INCLUDE 'hello_sub.interface'
+#endif
+INCLUDE 'hello_sub2.interface'
+
+CHARACTER (LEN=*), PARAMETER :: this = 'Hello'
+
+WRITE (*, '(A)') this // ': ' // TRIM (hello_string)
+#if defined(CALL_HELLO_SUB)
+CALL Hello_Sub (HUGE(0))
+#endif
+CALL Hello_Sub2 ()
+
+END PROGRAM Hello
Index: /OFFICIAL/FCM_V1.5/test/repos/add_subroutine/hello_sub2.f90
===================================================================
--- /OFFICIAL/FCM_V1.5/test/repos/add_subroutine/hello_sub2.f90	(revision 12)
+++ /OFFICIAL/FCM_V1.5/test/repos/add_subroutine/hello_sub2.f90	(revision 12)
@@ -0,0 +1,11 @@
+SUBROUTINE Hello_Sub2
+
+USE Hello_Constants, ONLY: hello_string
+
+IMPLICIT NONE
+
+CHARACTER (LEN=*), PARAMETER :: this = 'Hello_Sub2'
+
+WRITE (*, '(A)') this // ': ' // TRIM (hello_string)
+
+END SUBROUTINE Hello_Sub2
