#!/usr/bin/perl

=head1 NAME

dapper - A publishing tool for static websites.

=cut

use open ':std', ':encoding(UTF-8)';
use 5.010;

use strict;
use warnings;

use FindBin;
use lib "$FindBin::Bin/../lib";

use Pod::Usage;
use Getopt::Long qw(:config bundling no_ignore_case);
use App::Dapper;
use JSON ();

my $SOURCE  = undef;
my $OUTPUT  = undef;
my $LAYOUT  = undef;
my $CONFIG  = undef;
my $PORT    = 8000;
my $QUIET   = 0;
my $HELP    = 0;
my $VERSION = 0;
my $JSON    = 0;
my $TEMPLATE = undef;
my $DIRECTORY = undef;
my $DRY_RUN  = 0;
my $FORCE    = 0;
my $NO_BUILD = 0;

=head1 SYNOPSIS

    dapper [options] <command>

    # Initialize the current directory with a fresh skeleton of a site
    $ dapper init

    # ...or with a complete starter blog
    $ dapper init --template blog

    # ...or a portfolio, business or docs site, or a theme from git
    $ dapper init --template business
    $ dapper init --template https://github.com/someone/dapper-theme.git

    # List the starter templates
    $ dapper templates

    # Build the site
    $ dapper build

    # Build the site, then serve it locally at http://localhost:8000,
    # rebuilding whenever a file changes
    $ dapper serve

    # Rebuild the site whenever a file (source, layout, config) changes
    $ dapper watch

    # Check the site for problems (add --json for a machine-readable report)
    $ dapper check

    # Build, check and publish the site (to S3, GitHub Pages, or a command)
    $ dapper deploy
    $ dapper deploy --dry-run
    $ dapper deploy s3    # with several targets, name one

    # Let AI assistants work on the site over MCP
    $ dapper -C /path/to/site mcp

    # Write or update AGENTS.md, which explains the project to AI coding agents
    $ dapper agents

    # Get help on usage and switches
    $ dapper -h

    # Print the version
    $ dapper -v

=head1 DESCRIPTION

B<Dapper> transforms simple text files into static websites. Source files
(Markdown with YAML front matter) are combined with layout templates and
written to an output directory.

See L<App::Dapper> or the README at L<https://github.com/markdbenson/dapper>
for the full documentation: the project file, source files, layouts,
variables, filters and deployment.

=head1 OPTIONS

=over 4

=item B<-s>, B<--source>=I<source directory>

Specify the directory containing source files to process. If this command line option is not present, it defaults to the C<source> setting in the config file, or "_source".

=item B<-o>, B<--output>=I<output directory>

Specify the directory to place the output files in. If this command line option is not present, it defaults to the C<output> setting in the config file, or "_output".

=item B<-l>, B<--layout>=I<layout directory>

Specify the directory containing layout files. If this command line option is not present, it defaults to the C<layout> setting in the config file, or "_layout".

=item B<-c>, B<--config>=I<config file>

Specify the config file name to use. If this command line option is not present, it defaults to "_config.yml".

=item B<-p>, B<--port>=I<port>

Specify the port to use for the serve command. This option is only used for the serve command. If this command line option is not present, it defaults to "8000".

=item B<-q>, B<--quiet>

Only print errors.

=item B<-C>, B<--directory>=I<directory>

Run in this directory instead of the current one.

=item B<-t>, B<--template>=I<name>

For the init command: the starter to create, a built-in template (see
C<dapper templates>), a directory, or a git URL.

=item B<--dry-run>, B<--force>, B<--no-build>

For the deploy command: show what would happen without publishing; deploy even
if C<dapper check> finds errors; deploy the output directory without building
it first.

=item B<--json>

For the check command: print the report as JSON.

=item B<-h>, B<--help>

Get help on available commands and options.

=item B<-v>, B<--version>

Print the version and exit.

=back

=cut

sub dapper {
    my $d = App::Dapper->new($SOURCE, $OUTPUT, $LAYOUT, $CONFIG);
    $d->{quiet} = $QUIET;
    $d->{template} = $TEMPLATE;
    return $d;
}

sub init {
    dapper()->init();
}

sub build {
    dapper()->build();
}

sub watch {
    dapper()->watch();
}

sub check {
    my $report = dapper()->check();
    if ($JSON) {
        print JSON->new->canonical->pretty->encode($report);
    }
    else {
        print App::Dapper::Check::text($report);
    }
    exit($report->{ok} ? 0 : 1);
}

sub serve {
    my $pid = fork();
    die "error: could not fork: $!\n" unless defined $pid;
    if ($pid == 0) {
        my $d = dapper();
        $d->{live_reload} = 1;
        $d->watch();
        exit(0);
    }
    dapper()->serve($PORT);
}

sub help {
    my ($status) = @_;
    pod2usage({ -verbose => 1, -exitval => defined $status ? $status : 0 });
}

sub version {
    print "Dapper version $App::Dapper::VERSION\n";
    exit(0);
}

GetOptions(
    's|source=s' => \$SOURCE,
    'o|output=s' => \$OUTPUT,
    'l|layout=s' => \$LAYOUT,
    'c|config=s' => \$CONFIG,
    'p|port=i'   => \$PORT,
    'q|quiet'    => \$QUIET,
    'h|help'     => \$HELP,
    'v|version'  => \$VERSION,
    'json'       => \$JSON,
    't|template=s' => \$TEMPLATE,
    'C|directory=s' => \$DIRECTORY,
    'dry-run'    => \$DRY_RUN,
    'force'      => \$FORCE,
    'no-build'   => \$NO_BUILD,
) or help(2);

help(0) if $HELP;
version() if $VERSION;

if (defined $DIRECTORY) {
    chdir $DIRECTORY or do { print STDERR "error: could not change to \"$DIRECTORY\": $!\n"; exit(1) };
}

my $COMMAND = shift @ARGV;
help(0) if not defined $COMMAND or $COMMAND eq 'help';

my $switch = {
    'init'  => \&init,
    'build' => \&build,
    'serve' => \&serve,
    'watch' => \&watch,
    'check' => \&check,
    'deploy' => sub { dapper()->deploy(shift(@ARGV), dry_run => $DRY_RUN, force => $FORCE, no_build => $NO_BUILD) },
    'mcp'   => sub { dapper()->mcp() },
    'agents' => sub { dapper()->agents() },
    'templates' => sub { print map { sprintf("%-10s %s\n", $_, App::Dapper::Init::description($_)) } App::Dapper::Init::templates() },
};

if (not $switch->{$COMMAND}) {
    print STDERR "error: unknown command \"$COMMAND\"\n\n";
    help(2);
}

eval { $switch->{$COMMAND}->(); 1 } or do {
    print STDERR $@;
    exit(1);
};

exit(0);

=head1 AUTHOR

Mark Benson, C<< <mark at jayber.org> >>

=head1 BUGS

Please report any bugs or feature requests at
L<https://github.com/markdbenson/dapper/issues>.

=head1 LICENSE AND COPYRIGHT

The MIT License (MIT)

Copyright (c) 2002-2026 Mark Benson

See L<App::Dapper> for the full license text.

=cut
