113498266Sopenharmony_ci#!/usr/bin/env perl
213498266Sopenharmony_ci#***************************************************************************
313498266Sopenharmony_ci#                                  _   _ ____  _
413498266Sopenharmony_ci#  Project                     ___| | | |  _ \| |
513498266Sopenharmony_ci#                             / __| | | | |_) | |
613498266Sopenharmony_ci#                            | (__| |_| |  _ <| |___
713498266Sopenharmony_ci#                             \___|\___/|_| \_\_____|
813498266Sopenharmony_ci#
913498266Sopenharmony_ci# Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
1013498266Sopenharmony_ci#
1113498266Sopenharmony_ci# This software is licensed as described in the file COPYING, which
1213498266Sopenharmony_ci# you should have received as part of this distribution. The terms
1313498266Sopenharmony_ci# are also available at https://curl.se/docs/copyright.html.
1413498266Sopenharmony_ci#
1513498266Sopenharmony_ci# You may opt to use, copy, modify, merge, publish, distribute and/or sell
1613498266Sopenharmony_ci# copies of the Software, and permit persons to whom the Software is
1713498266Sopenharmony_ci# furnished to do so, under the terms of the COPYING file.
1813498266Sopenharmony_ci#
1913498266Sopenharmony_ci# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
2013498266Sopenharmony_ci# KIND, either express or implied.
2113498266Sopenharmony_ci#
2213498266Sopenharmony_ci# SPDX-License-Identifier: curl
2313498266Sopenharmony_ci#
2413498266Sopenharmony_ci###########################################################################
2513498266Sopenharmony_ci
2613498266Sopenharmony_ci=begin comment
2713498266Sopenharmony_ci
2813498266Sopenharmony_ciConverts a curldown file to nroff (man page).
2913498266Sopenharmony_ci
3013498266Sopenharmony_ci=end comment
3113498266Sopenharmony_ci=cut
3213498266Sopenharmony_ci
3313498266Sopenharmony_ciuse strict;
3413498266Sopenharmony_ciuse warnings;
3513498266Sopenharmony_ci
3613498266Sopenharmony_cimy $cd2nroff = "0.1"; # to keep check
3713498266Sopenharmony_cimy $dir;
3813498266Sopenharmony_cimy $extension;
3913498266Sopenharmony_cimy $keepfilename;
4013498266Sopenharmony_ci
4113498266Sopenharmony_ciwhile(@ARGV) {
4213498266Sopenharmony_ci    if($ARGV[0] eq "-d") {
4313498266Sopenharmony_ci        shift @ARGV;
4413498266Sopenharmony_ci        $dir = shift @ARGV;
4513498266Sopenharmony_ci    }
4613498266Sopenharmony_ci    elsif($ARGV[0] eq "-e") {
4713498266Sopenharmony_ci        shift @ARGV;
4813498266Sopenharmony_ci        $extension = shift @ARGV;
4913498266Sopenharmony_ci    }
5013498266Sopenharmony_ci    elsif($ARGV[0] eq "-k") {
5113498266Sopenharmony_ci        shift @ARGV;
5213498266Sopenharmony_ci        $keepfilename = 1;
5313498266Sopenharmony_ci    }
5413498266Sopenharmony_ci    elsif($ARGV[0] eq "-h") {
5513498266Sopenharmony_ci        print <<HELP
5613498266Sopenharmony_ciUsage: cd2nroff [options] [file.md]
5713498266Sopenharmony_ci
5813498266Sopenharmony_ci-d <dir> Write the output to the file name from the meta-data in the
5913498266Sopenharmony_ci         specified directory, instead of writing to stdout
6013498266Sopenharmony_ci-e <ext> If -d is used, this option can provide an added "extension", arbitrary
6113498266Sopenharmony_ci         text really, to append to the file name.
6213498266Sopenharmony_ci-h       This help text,
6313498266Sopenharmony_ci-v       Show version then exit
6413498266Sopenharmony_ciHELP
6513498266Sopenharmony_ci            ;
6613498266Sopenharmony_ci        exit 0;
6713498266Sopenharmony_ci    }
6813498266Sopenharmony_ci    elsif($ARGV[0] eq "-v") {
6913498266Sopenharmony_ci        print "cd2nroff version $cd2nroff\n";
7013498266Sopenharmony_ci        exit 0;
7113498266Sopenharmony_ci    }
7213498266Sopenharmony_ci    else {
7313498266Sopenharmony_ci        last;
7413498266Sopenharmony_ci    }
7513498266Sopenharmony_ci}
7613498266Sopenharmony_ci
7713498266Sopenharmony_ciuse POSIX qw(strftime);
7813498266Sopenharmony_cimy @ts;
7913498266Sopenharmony_ciif (defined($ENV{SOURCE_DATE_EPOCH})) {
8013498266Sopenharmony_ci    @ts = localtime($ENV{SOURCE_DATE_EPOCH});
8113498266Sopenharmony_ci} else {
8213498266Sopenharmony_ci    @ts = localtime;
8313498266Sopenharmony_ci}
8413498266Sopenharmony_cimy $date = strftime "%B %d %Y", @ts;
8513498266Sopenharmony_ci
8613498266Sopenharmony_cisub outseealso {
8713498266Sopenharmony_ci    my (@sa) = @_;
8813498266Sopenharmony_ci    my $comma = 0;
8913498266Sopenharmony_ci    my @o;
9013498266Sopenharmony_ci    push @o, ".SH SEE ALSO\n";
9113498266Sopenharmony_ci    for my $s (sort @sa) {
9213498266Sopenharmony_ci        push @o, sprintf "%s.BR $s", $comma ? ",\n": "";
9313498266Sopenharmony_ci        $comma = 1;
9413498266Sopenharmony_ci    }
9513498266Sopenharmony_ci    push @o, "\n";
9613498266Sopenharmony_ci    return @o;
9713498266Sopenharmony_ci}
9813498266Sopenharmony_ci
9913498266Sopenharmony_cisub single {
10013498266Sopenharmony_ci    my @seealso;
10113498266Sopenharmony_ci    my $d;
10213498266Sopenharmony_ci    my ($f)=@_;
10313498266Sopenharmony_ci    my $copyright;
10413498266Sopenharmony_ci    my $errors = 0;
10513498266Sopenharmony_ci    my $fh;
10613498266Sopenharmony_ci    my $line;
10713498266Sopenharmony_ci    my $salist;
10813498266Sopenharmony_ci    my $section;
10913498266Sopenharmony_ci    my $source;
11013498266Sopenharmony_ci    my $spdx;
11113498266Sopenharmony_ci    my $start = 0;
11213498266Sopenharmony_ci    my $title;
11313498266Sopenharmony_ci
11413498266Sopenharmony_ci    if(defined($f)) {
11513498266Sopenharmony_ci        if(!open($fh, "<:crlf", "$f")) {
11613498266Sopenharmony_ci            print STDERR "Failed to open $f : $!\n";
11713498266Sopenharmony_ci            return 1;
11813498266Sopenharmony_ci        }
11913498266Sopenharmony_ci    }
12013498266Sopenharmony_ci    else {
12113498266Sopenharmony_ci        $f = "STDIN";
12213498266Sopenharmony_ci        $fh = \*STDIN;
12313498266Sopenharmony_ci        binmode($fh, ":crlf");
12413498266Sopenharmony_ci    }
12513498266Sopenharmony_ci    while(<$fh>) {
12613498266Sopenharmony_ci        $line++;
12713498266Sopenharmony_ci        if(!$start) {
12813498266Sopenharmony_ci            if(/^---/) {
12913498266Sopenharmony_ci                # header starts here
13013498266Sopenharmony_ci                $start = 1;
13113498266Sopenharmony_ci            }
13213498266Sopenharmony_ci            next;
13313498266Sopenharmony_ci        }
13413498266Sopenharmony_ci        if(/^Title: *(.*)/i) {
13513498266Sopenharmony_ci            $title=$1;
13613498266Sopenharmony_ci        }
13713498266Sopenharmony_ci        elsif(/^Section: *(.*)/i) {
13813498266Sopenharmony_ci            $section=$1;
13913498266Sopenharmony_ci        }
14013498266Sopenharmony_ci        elsif(/^Source: *(.*)/i) {
14113498266Sopenharmony_ci            $source=$1;
14213498266Sopenharmony_ci        }
14313498266Sopenharmony_ci        elsif(/^See-also: +(.*)/i) {
14413498266Sopenharmony_ci            $salist = 0;
14513498266Sopenharmony_ci            push @seealso, $1;
14613498266Sopenharmony_ci        }
14713498266Sopenharmony_ci        elsif(/^See-also: */i) {
14813498266Sopenharmony_ci            if($seealso[0]) {
14913498266Sopenharmony_ci                print STDERR "$f:$line:1:ERROR: bad See-Also, needs list\n";
15013498266Sopenharmony_ci                return 2;
15113498266Sopenharmony_ci            }
15213498266Sopenharmony_ci            $salist = 1;
15313498266Sopenharmony_ci        }
15413498266Sopenharmony_ci        elsif(/^ +- (.*)/i) {
15513498266Sopenharmony_ci            # the only list we support is the see-also
15613498266Sopenharmony_ci            if($salist) {
15713498266Sopenharmony_ci                push @seealso, $1;
15813498266Sopenharmony_ci            }
15913498266Sopenharmony_ci        }
16013498266Sopenharmony_ci        # REUSE-IgnoreStart
16113498266Sopenharmony_ci        elsif(/^C: (.*)/i) {
16213498266Sopenharmony_ci            $copyright=$1;
16313498266Sopenharmony_ci        }
16413498266Sopenharmony_ci        elsif(/^SPDX-License-Identifier: (.*)/i) {
16513498266Sopenharmony_ci            $spdx=$1;
16613498266Sopenharmony_ci        }
16713498266Sopenharmony_ci        # REUSE-IgnoreEnd
16813498266Sopenharmony_ci        elsif(/^---/) {
16913498266Sopenharmony_ci            # end of the header section
17013498266Sopenharmony_ci            if(!$title) {
17113498266Sopenharmony_ci                print STDERR "ERROR: no 'Title:' in $f\n";
17213498266Sopenharmony_ci                return 1;
17313498266Sopenharmony_ci            }
17413498266Sopenharmony_ci            if(!$section) {
17513498266Sopenharmony_ci                print STDERR "ERROR: no 'Section:' in $f\n";
17613498266Sopenharmony_ci                return 2;
17713498266Sopenharmony_ci            }
17813498266Sopenharmony_ci            if(!$seealso[0]) {
17913498266Sopenharmony_ci                print STDERR "$f:$line:1:ERROR: no 'See-also:' present\n";
18013498266Sopenharmony_ci                return 2;
18113498266Sopenharmony_ci            }
18213498266Sopenharmony_ci            if(!$copyright) {
18313498266Sopenharmony_ci                print STDERR "$f:$line:1:ERROR: no 'C:' field present\n";
18413498266Sopenharmony_ci                return 2;
18513498266Sopenharmony_ci            }
18613498266Sopenharmony_ci            if(!$spdx) {
18713498266Sopenharmony_ci                print STDERR "$f:$line:1:ERROR: no 'SPDX-License-Identifier:' field present\n";
18813498266Sopenharmony_ci                return 2;
18913498266Sopenharmony_ci            }
19013498266Sopenharmony_ci            last;
19113498266Sopenharmony_ci        }
19213498266Sopenharmony_ci        else {
19313498266Sopenharmony_ci            chomp;
19413498266Sopenharmony_ci            print STDERR "WARN: unrecognized line in $f, ignoring:\n:'$_';"
19513498266Sopenharmony_ci        }
19613498266Sopenharmony_ci    }
19713498266Sopenharmony_ci
19813498266Sopenharmony_ci    if(!$start) {
19913498266Sopenharmony_ci        print STDERR "$f:$line:1:ERROR: no header present\n";
20013498266Sopenharmony_ci        return 2;
20113498266Sopenharmony_ci    }
20213498266Sopenharmony_ci
20313498266Sopenharmony_ci    my @desc;
20413498266Sopenharmony_ci    my $quote = 0;
20513498266Sopenharmony_ci    my $blankline = 0;
20613498266Sopenharmony_ci    my $header = 0;
20713498266Sopenharmony_ci
20813498266Sopenharmony_ci    # cut off the leading path from the file name, if any
20913498266Sopenharmony_ci    $f =~ s/^(.*[\\\/])//;
21013498266Sopenharmony_ci
21113498266Sopenharmony_ci    push @desc, ".\\\" generated by cd2nroff $cd2nroff from $f\n";
21213498266Sopenharmony_ci    push @desc, ".TH $title $section \"$date\" $source\n";
21313498266Sopenharmony_ci    while(<$fh>) {
21413498266Sopenharmony_ci        $line++;
21513498266Sopenharmony_ci
21613498266Sopenharmony_ci        $d = $_;
21713498266Sopenharmony_ci
21813498266Sopenharmony_ci        if($quote) {
21913498266Sopenharmony_ci            if($quote == 4) {
22013498266Sopenharmony_ci                # remove the indentation
22113498266Sopenharmony_ci                if($d =~ /^    (.*)/) {
22213498266Sopenharmony_ci                    push @desc, "$1\n";
22313498266Sopenharmony_ci                    next;
22413498266Sopenharmony_ci                }
22513498266Sopenharmony_ci                else {
22613498266Sopenharmony_ci                    # end of quote
22713498266Sopenharmony_ci                    $quote = 0;
22813498266Sopenharmony_ci                    push @desc, ".fi\n";
22913498266Sopenharmony_ci                    next;
23013498266Sopenharmony_ci                }
23113498266Sopenharmony_ci            }
23213498266Sopenharmony_ci            if(/^~~~/) {
23313498266Sopenharmony_ci                # end of quote
23413498266Sopenharmony_ci                $quote = 0;
23513498266Sopenharmony_ci                push @desc, ".fi\n";
23613498266Sopenharmony_ci                next;
23713498266Sopenharmony_ci            }
23813498266Sopenharmony_ci            # convert single backslahes to doubles
23913498266Sopenharmony_ci            $d =~ s/\\/\\\\/g;
24013498266Sopenharmony_ci            # lines starting with a period needs it escaped
24113498266Sopenharmony_ci            $d =~ s/^\./\\&./;
24213498266Sopenharmony_ci            push @desc, $d;
24313498266Sopenharmony_ci            next;
24413498266Sopenharmony_ci        }
24513498266Sopenharmony_ci
24613498266Sopenharmony_ci        # **bold**
24713498266Sopenharmony_ci        $d =~ s/\*\*(\S.*?)\*\*/\\fB$1\\fP/g;
24813498266Sopenharmony_ci        # *italics*
24913498266Sopenharmony_ci        $d =~ s/\*(\S.*?)\*/\\fI$1\\fP/g;
25013498266Sopenharmony_ci
25113498266Sopenharmony_ci        # mentions of curl symbols with man pages use italics by default
25213498266Sopenharmony_ci        $d =~ s/((lib|)curl([^ ]*\(3\)))/\\fI$1\\fP/gi;
25313498266Sopenharmony_ci
25413498266Sopenharmony_ci        # backticked becomes italics
25513498266Sopenharmony_ci        $d =~ s/\`(.*?)\`/\\fI$1\\fP/g;
25613498266Sopenharmony_ci
25713498266Sopenharmony_ci        if(/^## (.*)/) {
25813498266Sopenharmony_ci            my $word = $1;
25913498266Sopenharmony_ci            # if there are enclosing quotes, remove them first
26013498266Sopenharmony_ci            $word =~ s/[\"\'](.*)[\"\']\z/$1/;
26113498266Sopenharmony_ci
26213498266Sopenharmony_ci            # enclose in double quotes if there is a space present
26313498266Sopenharmony_ci            if($word =~ / /) {
26413498266Sopenharmony_ci                push @desc, ".IP \"$word\"\n";
26513498266Sopenharmony_ci            }
26613498266Sopenharmony_ci            else {
26713498266Sopenharmony_ci                push @desc, ".IP $word\n";
26813498266Sopenharmony_ci            }
26913498266Sopenharmony_ci            $header = 1;
27013498266Sopenharmony_ci        }
27113498266Sopenharmony_ci        elsif(/^# (.*)/) {
27213498266Sopenharmony_ci            my $word = $1;
27313498266Sopenharmony_ci            # if there are enclosing quotes, remove them first
27413498266Sopenharmony_ci            $word =~ s/[\"\'](.*)[\"\']\z/$1/;
27513498266Sopenharmony_ci            push @desc, ".SH $word\n";
27613498266Sopenharmony_ci            $header = 1;
27713498266Sopenharmony_ci        }
27813498266Sopenharmony_ci        elsif(/^~~~c/) {
27913498266Sopenharmony_ci            # start of a code section, not indented
28013498266Sopenharmony_ci            $quote = 1;
28113498266Sopenharmony_ci            push @desc, "\n" if($blankline && !$header);
28213498266Sopenharmony_ci            $header = 0;
28313498266Sopenharmony_ci            push @desc, ".nf\n";
28413498266Sopenharmony_ci        }
28513498266Sopenharmony_ci        elsif(/^~~~/) {
28613498266Sopenharmony_ci            # start of a quote section; not code, not indented
28713498266Sopenharmony_ci            $quote = 1;
28813498266Sopenharmony_ci            push @desc, "\n" if($blankline && !$header);
28913498266Sopenharmony_ci            $header = 0;
29013498266Sopenharmony_ci            push @desc, ".nf\n";
29113498266Sopenharmony_ci        }
29213498266Sopenharmony_ci        elsif(/^    (.*)/) {
29313498266Sopenharmony_ci            # quoted, indented by 4 space
29413498266Sopenharmony_ci            $quote = 4;
29513498266Sopenharmony_ci            push @desc, "\n" if($blankline && !$header);
29613498266Sopenharmony_ci            $header = 0;
29713498266Sopenharmony_ci            push @desc, ".nf\n$1\n";
29813498266Sopenharmony_ci        }
29913498266Sopenharmony_ci        elsif(/^[ \t]*\n/) {
30013498266Sopenharmony_ci            # count and ignore blank lines
30113498266Sopenharmony_ci            $blankline++;
30213498266Sopenharmony_ci        }
30313498266Sopenharmony_ci        else {
30413498266Sopenharmony_ci            # don't output newlines if this is the first content after a
30513498266Sopenharmony_ci            # header
30613498266Sopenharmony_ci            push @desc, "\n" if($blankline && !$header);
30713498266Sopenharmony_ci            $blankline = 0;
30813498266Sopenharmony_ci            $header = 0;
30913498266Sopenharmony_ci
31013498266Sopenharmony_ci            # remove single line HTML comments
31113498266Sopenharmony_ci            $d =~ s/<!--.*?-->//g;
31213498266Sopenharmony_ci
31313498266Sopenharmony_ci            # quote minuses in the output
31413498266Sopenharmony_ci            $d =~ s/([^\\])-/$1\\-/g;
31513498266Sopenharmony_ci            # replace single quotes
31613498266Sopenharmony_ci            $d =~ s/\'/\\(aq/g;
31713498266Sopenharmony_ci            # handle double quotes first on the line
31813498266Sopenharmony_ci            $d =~ s/^(\s*)\"/$1\\&\"/;
31913498266Sopenharmony_ci
32013498266Sopenharmony_ci            # lines starting with a period needs it escaped
32113498266Sopenharmony_ci            $d =~ s/^\./\\&./;
32213498266Sopenharmony_ci
32313498266Sopenharmony_ci            if($d =~ /^(.*)  /) {
32413498266Sopenharmony_ci                printf STDERR "$f:$line:%d:ERROR: 2 spaces detected\n",
32513498266Sopenharmony_ci                    length($1);
32613498266Sopenharmony_ci                $errors++;
32713498266Sopenharmony_ci            }
32813498266Sopenharmony_ci            if($d =~ /^[ \t]*\n/) {
32913498266Sopenharmony_ci                # replaced away all contents
33013498266Sopenharmony_ci                $blankline= 1;
33113498266Sopenharmony_ci            }
33213498266Sopenharmony_ci            else {
33313498266Sopenharmony_ci                push @desc, $d;
33413498266Sopenharmony_ci            }
33513498266Sopenharmony_ci        }
33613498266Sopenharmony_ci    }
33713498266Sopenharmony_ci    if($fh != \*STDIN) {
33813498266Sopenharmony_ci        close($fh);
33913498266Sopenharmony_ci    }
34013498266Sopenharmony_ci    push @desc, outseealso(@seealso);
34113498266Sopenharmony_ci    if($dir) {
34213498266Sopenharmony_ci        if($keepfilename) {
34313498266Sopenharmony_ci            $title = $f;
34413498266Sopenharmony_ci            $title =~ s/\.[^.]*$//;
34513498266Sopenharmony_ci        }
34613498266Sopenharmony_ci        my $outfile = "$dir/$title.$section";
34713498266Sopenharmony_ci        if(defined($extension)) {
34813498266Sopenharmony_ci            $outfile .= $extension;
34913498266Sopenharmony_ci        }
35013498266Sopenharmony_ci        if(!open(O, ">", $outfile)) {
35113498266Sopenharmony_ci            print STDERR "Failed to open $outfile : $!\n";
35213498266Sopenharmony_ci            return 1;
35313498266Sopenharmony_ci        }
35413498266Sopenharmony_ci        print O @desc;
35513498266Sopenharmony_ci        close(O);
35613498266Sopenharmony_ci    }
35713498266Sopenharmony_ci    else {
35813498266Sopenharmony_ci        print @desc;
35913498266Sopenharmony_ci    }
36013498266Sopenharmony_ci    return $errors;
36113498266Sopenharmony_ci}
36213498266Sopenharmony_ci
36313498266Sopenharmony_ciif(@ARGV) {
36413498266Sopenharmony_ci    for my $f (@ARGV) {
36513498266Sopenharmony_ci        my $r = single($f);
36613498266Sopenharmony_ci        if($r) {
36713498266Sopenharmony_ci            exit $r;
36813498266Sopenharmony_ci        }
36913498266Sopenharmony_ci    }
37013498266Sopenharmony_ci}
37113498266Sopenharmony_cielse {
37213498266Sopenharmony_ci    exit single();
37313498266Sopenharmony_ci}
374