#!/usr/bin/env perl
#
# picotool uses the custom command line parser cli.h
# this tool converts that output to markdown
# that in turn can be turned into roff by pandoc

use strict;
use warnings;
use v5.40.1;

my $subcommand = $ARGV[0];

# For the title in the top left corner and for the NAME section we use a
# hyphen as a separator. Otherwise, lexgrog also cannot grog the line.
my $hyphenedcmd = "picotool $subcommand";
$hyphenedcmd =~ s/ /-/g;

sub escape {
    my $line = shift;
    $line =~ s/([\\*_\{\}\[\]\<\>()#+\-.\!\|])/\\$1/g;
    return $line;
}

sub print_section {
    my $text = shift;
    print "# " . (escape($text)) . "\n\n";
}

sub print_synopsis {
    my $text = shift;
    foreach my $line (split /\n/, $text) {
        print escape($line) . "\n\n";
    }
    print "\n";
}

sub print_text {
    my $text = shift;
    print escape($text) . "\n\n";
}

sub print_opt {
    my $text = shift;
    my @args = ();
    foreach my $group (split ',', $text) {
        my ($name, $val) = split ' ', $group;
        if (defined $val) {
            $val =~ /^</ or die "invalid val: $val";
            $val =~ />$/ or die "invalid val: $val";
            $val =~ s/^<|>$//g;
            $name = escape($name);
            $val  = escape($val);
            push @args, "**$name** \\<*$val*\\>";
        } elsif ($name =~ /^</ and $name =~ />$/) {
            $name =~ s/^<|>$//g;
            $name = escape($name);
            push @args, "\\<*$name*\\>";
        } else {
            $name = escape($name);
            push @args, "**$name**";
        }
    }
    print(join ", ", @args);
    print "\n";
}

sub print_desc {
    my $text = shift;
    print ": " . (escape($text)) . "\n\n";
}

print <<"EOF";
---
title: @{[uc($hyphenedcmd)]}
section: 1
header: User Commands
---
EOF

my $section_nr   = undef;
my $section_name = undef;
my $state        = undef;
my $text         = undef;

while (my $line = <STDIN>) {
    if (!defined($state)) {
        $line =~ /^[^ ][A-Z0-9 -]+:$/ or die "expected section but got: $line";
        $line =~ s/:\n$//;
        $section_name = $line;
        $section_name eq uc($subcommand)
          or die "expected first section named $subcommand, got $line";
        $section_name = "NAME";
        $section_nr   = 1;
        $state        = 'SECTION';
    } elsif ($state eq 'SECTION') {
        print_section($section_name);

        # the content after a section is always indented
        $line =~ /^    /
          or die "expected indented content after section, got $line";
        if ($line =~ /^    (\S+)  +(\S+.*)/) {

            # aligned description output in COMMANDS section
            ($section_name eq "COMMANDS" or $section_name eq "SUB COMMANDS")
              or die
"aligned output only in (SUB) COMMAND section but got: $section_name";
            $state = 'COMMAND';
            print_opt($1);
            $text = $2;
        } elsif ($line =~ /^    \S/) {
            $line = trim($line);

            # make sure that only the first section can be named NAME
            ($section_nr == 1 xor $section_name eq "NAME")
              and die
"expected NAME as first section, got $section_name as option $section_nr";
            # make sure that only the second section can be named SYNOPSIS
            ($section_nr == 2 xor $section_name eq "SYNOPSIS")
              and die
"expected SYNOPSIS as second section, got $section_name as option $section_nr";
            if ($section_name eq "NAME") {
                # prefix the text in NAME section with the command name and
                # a hyphen to satisfy the format expected by lexgrog to
                # generate a database that can be queried by apropos and
                # whatis
                $text  = "$hyphenedcmd - $line";
                $state = 'TEXT';
            } elsif ($section_name eq "SYNOPSIS") {
                $text  = $line;
                $state = 'SYNOPSIS';
            } else {
                $text  = $line;
                $state = 'TEXT';
            }
        } elsif ($line =~ /^        [-<]/) {
            $line = trim($line);
            print_opt($line);
            $state = 'OPT';
        } else {
            die "unexpected content following SECTION: $line";
        }
    } elsif ($state eq 'TEXT') {
        if ($line eq "\n") {
            print_text($text);
            $text  = undef;
            $state = "EMPTY";
        } elsif ($line =~ /^    \S/) {
            $line = trim($line);
            $text .= " $line";
        } elsif ($line =~ /^            \S/) {

            # no idea why sometimes, the renderer indent a line in a paragraph
            $line = trim($line);
            $text .= " $line";
        } elsif ($line =~ /^        [-<]/) {
            print_text($text);
            $text = undef;
            $line = trim($line);
            print_opt($line);
            $state = 'OPT';
        } else {
            die "TEXT: unexpected: $line";
        }
    } elsif ($state eq 'SYNOPSIS') {
        if ($line eq "\n") {
            print_synopsis($text);
            $text  = undef;
            $state = "EMPTY";
        } elsif ($line =~ /^    \S/) {

            # new entry
            $line = trim($line);
            $text .= "\n$line";
        } elsif ($line =~ /^                \S/) {

            # continuation line
            $line = trim($line);
            $text .= " $line";
        } else {
            die "unexpected synopsis: $line";
        }
    } elsif ($state eq 'EMPTY') {
        if ($line eq "Use \"picotool help <cmd>\" for more info\n") {

            # ignore this line
            next;
        } elsif ($line =~ /^[^ ][A-Z ]+:$/) {
            $line =~ s/:\n$//;
            $section_name = $line;
            $section_nr++;
            $state = 'SECTION';
        } elsif ($line =~ /^            \S/) {

           # crude hack to support the multi-line description in "picotool help
           # otp list" which is not possible to represent in markdown
            $line  = trim($line);
            $text  = $line;
            $state = 'TEXT';
        } else {
            die "unexpected after empty: $line";
        }
    } elsif ($state eq 'OPT') {

        # after an option follows its description
        $line =~ /^            \S/ or die "unexpected description: $line";
        $line  = trim($line);
        $text  = $line;
        $state = 'DESC';
    } elsif ($state eq 'DESC') {

        # after a description follows more description, a new opt, text or an
        # empty line
        if ($line =~ /^            \S/) {
            $line = trim($line);
            $text .= $line;
        } elsif ($line =~ /^        [-<]/) {
            print_desc($text);
            $text = undef;
            $line = trim($line);
            print_opt($line);
            $state = 'OPT';
        } elsif ($line =~ /^    \S/) {
            print_desc($text);
            $line  = trim($line);
            $text  = $line;
            $state = 'TEXT';
        } elsif ($line eq "\n") {
            print_desc($text);
            $text  = undef;
            $state = "EMPTY";
        } else {
            die "unexpected content following DESC: $line";
        }
    } elsif ($state eq 'COMMAND') {

        # after a command follows its continuation or a new command or an
        # empty line
        if ($line eq "\n") {
            print_desc($text);
            $text  = undef;
            $state = "EMPTY";
        } elsif ($line =~ /^    (\S+)  +(\S+.*)/) {
            print_desc($text);
            print_opt($1);
            $text = $2;
        } elsif ($line =~ /^                \S/) {
            $line = trim($line);
            $text .= $line;
        } elsif ($line =~ /^                  \S/) {
            $line = trim($line);
            $text .= $line;
        } else {
            die "unknown content following command";
        }
    } else {
        die "unknown state: $state";
    }
}
if ($state eq 'EMPTY') {

    # nothing to do
} elsif ($state eq 'DESC') {
    print_desc($text);
} elsif ($state eq 'COMMAND') {
    print_desc($text);
} elsif ($state eq 'TEXT') {
    print_text($text);
} else {
    die "unsupported last state: $state";
}
