App-DocKnot

 view release on metacpan or  search on metacpan

lib/App/DocKnot/Update.pm  view on Meta::CPAN

    if (defined($repo)) {
        my $root = path($repo->work_tree());
        $repo->run('add', $spin_path->relative($root)->stringify());
        $repo->run('rm', $faq_path->relative($root)->stringify());
    }
    return;
}

# Convert an *.rpod file to a *.spin pointer.  Intended to be run via
# Path::Iterator::Rule.
#
# $rpod_path - Path to *.rpod file
# $repo      - Optional Git::Repository object for input tree
sub _convert_rpod_pointer {
    my ($self, $rpod_path, $repo) = @_;

    # Convert the file.
    my $data_ref = $self->_read_rpod_pointer($rpod_path);
    my $basename = $rpod_path->basename('.rpod');
    my $spin_path = $rpod_path->sibling($basename . '.spin');
    $self->_write_spin_pointer($data_ref, $spin_path);

    # If we have a Git repository, update Git.
    if (defined($repo)) {
        my $root = path($repo->work_tree());
        $repo->run('add', $spin_path->relative($root)->stringify());
        $repo->run('rm', $rpod_path->relative($root)->stringify());
    }
    return;
}

##############################################################################
# Public Interface
##############################################################################

# Create a new App::DocKnot::Update object, which will be used for subsequent
# calls.
#
# $args  - Anonymous hash of arguments with the following keys:
#   metadata - Path to the directory containing package metadata
#   output   - Path to the output file with the converted metadata
#
# Returns: Newly created object
#  Throws: Text exceptions on invalid metadata directory path
sub new {
    my ($class, $args_ref) = @_;
    my $self = {
        metadata => path($args_ref->{metadata} // 'docs/metadata'),
        output   => path($args_ref->{output} // 'docs/docknot.yaml'),
    };
    bless($self, $class);
    return $self;
}

# Update an older version of DocKnot configuration.  Currently, this only
# handles the old JSON format.
#
# Raises: autodie exception on failure to read metadata
#         Text exception on inconsistencies in the package data
#         Text exception if schema checking failed on the converted config
sub update {
    my ($self) = @_;

    # Ensure we were given a valid metadata argument.
    if (!$self->{metadata}->is_dir()) {
        my $metadata = $self->{metadata};
        croak("metadata path $metadata does not exist or is not a directory");
    }

    # Tell YAML::XS that we'll be feeding it JSON::PP booleans.
    local $YAML::XS::Boolean = 'JSON::PP';

    # Load the config.
    my $data_ref = $self->_config_from_json();

    # Add the current format version.
    $data_ref->{format} = 'v1';

    # Move bootstrap to build.bootstrap.
    if (defined($data_ref->{bootstrap})) {
        $data_ref->{build}{bootstrap} = $data_ref->{bootstrap};
        delete $data_ref->{bootstrap};
    }

    # Move build.lancaster to test.lancaster.
    if (defined($data_ref->{build}{lancaster})) {
        $data_ref->{test}{lancaster} = $data_ref->{build}{lancaster};
        delete $data_ref->{build}{lancaster};
    }

    # Move packaging.debian to packaging.debian.package, move debian to
    # packaging.debian, and move packaging to distribution.packaging.
    if (defined($data_ref->{packaging})) {
        if (defined($data_ref->{packaging}{debian})) {
            my $package = $data_ref->{packaging}{debian};
            $data_ref->{packaging}{debian} = { package => $package };
        }
    }
    if (defined($data_ref->{debian})) {
        $data_ref->{packaging}{debian} //= {};
        $data_ref->{packaging}{debian}
          = { $data_ref->{debian}->%*, $data_ref->{packaging}{debian}->%* };
        delete $data_ref->{debian};
    }
    if ($data_ref->{packaging}) {
        $data_ref->{distribution}{packaging} = $data_ref->{packaging};
        delete $data_ref->{packaging};
    }

    # Move readme.sections to sections.  If there was a testing override, move
    # it to test.override and delete it from sections.
    if (defined($data_ref->{readme})) {
        $data_ref->{sections} = $data_ref->{readme}{sections};
        delete $data_ref->{readme};
        for my $section_ref ($data_ref->{sections}->@*) {
            if (lc($section_ref->{title}) eq 'testing') {
                $data_ref->{test}{override} = $section_ref->{body};
                last;
            }
        }
        $data_ref->{sections}
          = [grep { lc($_->{title}) ne 'testing' } $data_ref->{sections}->@*];
    }

    # support.cpan is obsolete.  If vcs.github is set and support.github is
    # not, use it as support.github.
    if (defined($data_ref->{support}{cpan})) {
        if (!defined($data_ref->{support}{github})) {
            if (defined($data_ref->{vcs}{github})) {
                $data_ref->{support}{github} = $data_ref->{vcs}{github};
            }
        }
        delete $data_ref->{support}{cpan};
    }

    # Check the schema of the resulting file.
    my $schema_path = $self->appdata_path('schema/docknot.yaml');
    my $schema_ref = YAML::XS::LoadFile($schema_path);
    eval { validate($schema_ref, $data_ref) };
    if ($@) {
        my $errors = $@;
        chomp($errors);
        die "schema validation failed:\n$errors\n";
    }

    # Write the new YAML package configuration.
    YAML::XS::DumpFile($self->{output}->stringify(), $data_ref);
    return;
}

# Update an input tree for spin to the current format.
#
# $path - Optional path to the spin input tree, defaults to current directory
#
# Raises: Text exception on failure
sub update_spin {
    my ($self, $path) = @_;
    $path = defined($path) ? path($path) : path(q{.});
    my $repo;
    if ($path->child('.git')->is_dir()) {
        $repo = Git::Repository->new(work_tree => "$path");
    }

    # Convert all *.faq files to *.spin files.
    my $rule = Path::Iterator::Rule->new()->name(qr{ [.] faq \z }xms);
    my $iter = $rule->iter($path, { follow_symlinks => 0 });
    while (defined(my $file = $iter->())) {
        $self->_convert_faq_pointer(path($file), $repo);
    }

    # Convert all *.rpod files to *.spin files.
    $rule = Path::Iterator::Rule->new()->name(qr{ [.] rpod \z }xms);
    $iter = $rule->iter($path, { follow_symlinks => 0 });
    while (defined(my $file = $iter->())) {
        $self->_convert_rpod_pointer(path($file), $repo);
    }
    return;
}

##############################################################################
# Module return value and documentation
##############################################################################

1;
__END__

=for stopwords
Allbery DocKnot MERCHANTABILITY NONINFRINGEMENT sublicense CPAN XDG

=head1 NAME

App::DocKnot::Update - Update DocKnot input or package configuration

=head1 SYNOPSIS

    use App::DocKnot::Update;

    my $update = App::DocKnot::Update->new(
        {
            metadata => 'docs/metadata',
            output   => 'docs/docknot.yaml',
        }
    );
    $update->update();

    $update->update_spin('/path/to/spin/input');

=head1 REQUIREMENTS

Perl 5.24 or later and the modules Git::Repository, File::BaseDir,
File::ShareDir, JSON::MaybeXS, Path::Iterator::Rule, Path::Tiny, Perl6::Slurp,
and YAML::XS, all of which are available from CPAN.

=head1 DESCRIPTION

This component of DocKnot updates package configuration from older versions.



( run in 0.702 second using v1.01-cache-2.11-cpan-39bf76dae61 )