Merge branch 'jani-rest' into docs-next
authorJonathan Corbet <corbet@lwn.net>
Sat, 14 May 2016 16:21:57 +0000 (10:21 -0600)
committerJonathan Corbet <corbet@lwn.net>
Sat, 14 May 2016 16:21:57 +0000 (10:21 -0600)
Patch series from Jani Nikula:

> Jon, I was hoping we could consider nudging things forward a bit in the
> kernel-doc and docproc reStructuredText front already in 4.7. I know
> it's a bit close to the merge window, but this should not interfere with
> anything else, and some of it are just trivial cleanups that I've been
> carrying around locally.
>
> Obviously this doesn't actually add anything that uses them yet, but I
> think it would be helpful to have some common base in to ease
> collaboration.

scripts/docproc.c
scripts/kernel-doc

index e267e62..0a12593 100644 (file)
 #include <unistd.h>
 #include <limits.h>
 #include <errno.h>
+#include <getopt.h>
 #include <sys/types.h>
 #include <sys/wait.h>
+#include <time.h>
 
 /* exitstatus is used to keep track of any failing calls to kernel-doc,
  * but execution continues. */
@@ -68,12 +70,23 @@ FILELINE * docsection;
 #define KERNELDOCPATH "scripts/"
 #define KERNELDOC     "kernel-doc"
 #define DOCBOOK       "-docbook"
+#define RST           "-rst"
 #define LIST          "-list"
 #define FUNCTION      "-function"
 #define NOFUNCTION    "-nofunction"
 #define NODOCSECTIONS "-no-doc-sections"
 #define SHOWNOTFOUND  "-show-not-found"
 
+enum file_format {
+       FORMAT_AUTO,
+       FORMAT_DOCBOOK,
+       FORMAT_RST,
+};
+
+static enum file_format file_format = FORMAT_AUTO;
+
+#define KERNELDOC_FORMAT       (file_format == FORMAT_RST ? RST : DOCBOOK)
+
 static char *srctree, *kernsrctree;
 
 static char **all_list = NULL;
@@ -95,7 +108,7 @@ static void consume_symbol(const char *sym)
 
 static void usage (void)
 {
-       fprintf(stderr, "Usage: docproc {doc|depend} file\n");
+       fprintf(stderr, "Usage: docproc [{--docbook|--rst}] {doc|depend} file\n");
        fprintf(stderr, "Input is read from file.tmpl. Output is sent to stdout\n");
        fprintf(stderr, "doc: frontend when generating kernel documentation\n");
        fprintf(stderr, "depend: generate list of files referenced within file\n");
@@ -242,7 +255,7 @@ static void find_export_symbols(char * filename)
 /*
  * Document all external or internal functions in a file.
  * Call kernel-doc with following parameters:
- * kernel-doc -docbook -nofunction function_name1 filename
+ * kernel-doc [-docbook|-rst] -nofunction function_name1 filename
  * Function names are obtained from all the src files
  * by find_export_symbols.
  * intfunc uses -nofunction
@@ -263,7 +276,7 @@ static void docfunctions(char * filename, char * type)
                exit(1);
        }
        vec[idx++] = KERNELDOC;
-       vec[idx++] = DOCBOOK;
+       vec[idx++] = KERNELDOC_FORMAT;
        vec[idx++] = NODOCSECTIONS;
        for (i=0; i < symfilecnt; i++) {
                struct symfile * sym = &symfilelist[i];
@@ -275,7 +288,10 @@ static void docfunctions(char * filename, char * type)
        }
        vec[idx++]     = filename;
        vec[idx] = NULL;
-       printf("<!-- %s -->\n", filename);
+       if (file_format == FORMAT_RST)
+               printf(".. %s\n", filename);
+       else
+               printf("<!-- %s -->\n", filename);
        exec_kernel_doc(vec);
        fflush(stdout);
        free(vec);
@@ -294,7 +310,7 @@ static void singfunc(char * filename, char * line)
        int i, idx = 0;
        int startofsym = 1;
        vec[idx++] = KERNELDOC;
-       vec[idx++] = DOCBOOK;
+       vec[idx++] = KERNELDOC_FORMAT;
        vec[idx++] = SHOWNOTFOUND;
 
        /* Split line up in individual parameters preceded by FUNCTION */
@@ -343,7 +359,7 @@ static void docsect(char *filename, char *line)
        free(s);
 
        vec[0] = KERNELDOC;
-       vec[1] = DOCBOOK;
+       vec[1] = KERNELDOC_FORMAT;
        vec[2] = SHOWNOTFOUND;
        vec[3] = FUNCTION;
        vec[4] = line;
@@ -430,6 +446,32 @@ static void find_all_symbols(char *filename)
        }
 }
 
+/*
+ * Terminate s at first space, if any. If there was a space, return pointer to
+ * the character after that. Otherwise, return pointer to the terminating NUL.
+ */
+static char *chomp(char *s)
+{
+       while (*s && !isspace(*s))
+               s++;
+
+       if (*s)
+               *s++ = '\0';
+
+       return s;
+}
+
+/* Return pointer to directive content, or NULL if not a directive. */
+static char *is_directive(char *line)
+{
+       if (file_format == FORMAT_DOCBOOK && line[0] == '!')
+               return line + 1;
+       else if (file_format == FORMAT_RST && !strncmp(line, ".. !", 4))
+               return line + 4;
+
+       return NULL;
+}
+
 /*
  * Parse file, calling action specific functions for:
  * 1) Lines containing !E
@@ -443,63 +485,75 @@ static void find_all_symbols(char *filename)
 static void parse_file(FILE *infile)
 {
        char line[MAXLINESZ];
-       char * s;
+       char *p, *s;
        while (fgets(line, MAXLINESZ, infile)) {
-               if (line[0] == '!') {
-                       s = line + 2;
-                       switch (line[1]) {
-                               case 'E':
-                                       while (*s && !isspace(*s)) s++;
-                                       *s = '\0';
-                                       externalfunctions(line+2);
-                                       break;
-                               case 'I':
-                                       while (*s && !isspace(*s)) s++;
-                                       *s = '\0';
-                                       internalfunctions(line+2);
-                                       break;
-                               case 'D':
-                                       while (*s && !isspace(*s)) s++;
-                                       *s = '\0';
-                                       symbolsonly(line+2);
-                                       break;
-                               case 'F':
-                                       /* filename */
-                                       while (*s && !isspace(*s)) s++;
-                                       *s++ = '\0';
-                                       /* function names */
-                                       while (isspace(*s))
-                                               s++;
-                                       singlefunctions(line +2, s);
-                                       break;
-                               case 'P':
-                                       /* filename */
-                                       while (*s && !isspace(*s)) s++;
-                                       *s++ = '\0';
-                                       /* DOC: section name */
-                                       while (isspace(*s))
-                                               s++;
-                                       docsection(line + 2, s);
-                                       break;
-                               case 'C':
-                                       while (*s && !isspace(*s)) s++;
-                                       *s = '\0';
-                                       if (findall)
-                                               findall(line+2);
-                                       break;
-                               default:
-                                       defaultline(line);
-                       }
-               } else {
+               p = is_directive(line);
+               if (!p) {
+                       defaultline(line);
+                       continue;
+               }
+
+               switch (*p++) {
+               case 'E':
+                       chomp(p);
+                       externalfunctions(p);
+                       break;
+               case 'I':
+                       chomp(p);
+                       internalfunctions(p);
+                       break;
+               case 'D':
+                       chomp(p);
+                       symbolsonly(p);
+                       break;
+               case 'F':
+                       /* filename */
+                       s = chomp(p);
+                       /* function names */
+                       while (isspace(*s))
+                               s++;
+                       singlefunctions(p, s);
+                       break;
+               case 'P':
+                       /* filename */
+                       s = chomp(p);
+                       /* DOC: section name */
+                       while (isspace(*s))
+                               s++;
+                       docsection(p, s);
+                       break;
+               case 'C':
+                       chomp(p);
+                       if (findall)
+                               findall(p);
+                       break;
+               default:
                        defaultline(line);
                }
        }
        fflush(stdout);
 }
 
+/*
+ * Is this a RestructuredText template?  Answer the question by seeing if its
+ * name ends in ".rst".
+ */
+static int is_rst(const char *file)
+{
+       char *dot = strrchr(file, '.');
+
+       return dot && !strcmp(dot + 1, "rst");
+}
+
+enum opts {
+       OPT_DOCBOOK,
+       OPT_RST,
+       OPT_HELP,
+};
 
 int main(int argc, char *argv[])
 {
+       const char *subcommand, *filename;
        FILE * infile;
        int i;
 
@@ -509,19 +563,66 @@ int main(int argc, char *argv[])
        kernsrctree = getenv("KBUILD_SRC");
        if (!kernsrctree || !*kernsrctree)
                kernsrctree = srctree;
-       if (argc != 3) {
+
+       for (;;) {
+               int c;
+               struct option opts[] = {
+                       { "docbook",    no_argument, NULL, OPT_DOCBOOK },
+                       { "rst",        no_argument, NULL, OPT_RST },
+                       { "help",       no_argument, NULL, OPT_HELP },
+                       {}
+               };
+
+               c = getopt_long_only(argc, argv, "", opts, NULL);
+               if (c == -1)
+                       break;
+
+               switch (c) {
+               case OPT_DOCBOOK:
+                       file_format = FORMAT_DOCBOOK;
+                       break;
+               case OPT_RST:
+                       file_format = FORMAT_RST;
+                       break;
+               case OPT_HELP:
+                       usage();
+                       return 0;
+               default:
+               case '?':
+                       usage();
+                       return 1;
+               }
+       }
+
+       argc -= optind;
+       argv += optind;
+
+       if (argc != 2) {
                usage();
                exit(1);
        }
+
+       subcommand = argv[0];
+       filename = argv[1];
+
+       if (file_format == FORMAT_AUTO)
+               file_format = is_rst(filename) ? FORMAT_RST : FORMAT_DOCBOOK;
+
        /* Open file, exit on error */
-       infile = fopen(argv[2], "r");
+       infile = fopen(filename, "r");
        if (infile == NULL) {
                fprintf(stderr, "docproc: ");
-               perror(argv[2]);
+               perror(filename);
                exit(2);
        }
 
-       if (strcmp("doc", argv[1]) == 0) {
+       if (strcmp("doc", subcommand) == 0) {
+               if (file_format == FORMAT_RST) {
+                       time_t t = time(NULL);
+                       printf(".. generated from %s by docproc %s\n",
+                              filename, ctime(&t));
+               }
+
                /* Need to do this in two passes.
                 * First pass is used to collect all symbols exported
                 * in the various files;
@@ -557,10 +658,10 @@ int main(int argc, char *argv[])
                        fprintf(stderr, "Warning: didn't use docs for %s\n",
                                all_list[i]);
                }
-       } else if (strcmp("depend", argv[1]) == 0) {
+       } else if (strcmp("depend", subcommand) == 0) {
                /* Create first part of dependency chain
                 * file.tmpl */
-               printf("%s\t", argv[2]);
+               printf("%s\t", filename);
                defaultline       = noaction;
                internalfunctions = adddep;
                externalfunctions = adddep;
@@ -571,7 +672,7 @@ int main(int argc, char *argv[])
                parse_file(infile);
                printf("\n");
        } else {
-               fprintf(stderr, "Unknown option: %s\n", argv[1]);
+               fprintf(stderr, "Unknown option: %s\n", subcommand);
                exit(1);
        }
        fclose(infile);
index c37255b..2fc8fad 100755 (executable)
@@ -39,41 +39,44 @@ use strict;
 # 25/07/2012 - Added support for HTML5
 # -- Dan Luedtke <mail@danrl.de>
 
-#
-# This will read a 'c' file and scan for embedded comments in the
-# style of gnome comments (+minor extensions - see below).
-#
-
-# Note: This only supports 'c'.
-
-# usage:
-# kernel-doc [ -docbook | -html | -html5 | -text | -man | -list ]
-#            [ -no-doc-sections ]
-#            [ -function funcname [ -function funcname ...] ]
-#            c file(s)s > outputfile
-# or
-#            [ -nofunction funcname [ -function funcname ...] ]
-#            c file(s)s > outputfile
-#
-#  Set output format using one of -docbook -html -html5 -text or -man.
-#  Default is man.
-#  The -list format is for internal use by docproc.
-#
-#  -no-doc-sections
-#      Do not output DOC: sections
-#
-#  -function funcname
-#      If set, then only generate documentation for the given function(s) or
-#      DOC: section titles.  All other functions and DOC: sections are ignored.
-#
-#  -nofunction funcname
-#      If set, then only generate documentation for the other function(s)/DOC:
-#      sections. Cannot be used together with -function (yes, that's a bug --
-#      perl hackers can fix it 8))
-#
-#  c files - list of 'c' files to process
-#
-#  All output goes to stdout, with errors to stderr.
+sub usage {
+    my $message = <<"EOF";
+Usage: $0 [OPTION ...] FILE ...
+
+Read C language source or header FILEs, extract embedded documentation comments,
+and print formatted documentation to standard output.
+
+The documentation comments are identified by "/**" opening comment mark. See
+Documentation/kernel-doc-nano-HOWTO.txt for the documentation comment syntax.
+
+Output format selection (mutually exclusive):
+  -docbook             Output DocBook format.
+  -html                        Output HTML format.
+  -html5               Output HTML5 format.
+  -list                        Output symbol list format. This is for use by docproc.
+  -man                 Output troff manual page format. This is the default.
+  -rst                 Output reStructuredText format.
+  -text                        Output plain text format.
+
+Output selection (mutually exclusive):
+  -function NAME       Only output documentation for the given function(s)
+                       or DOC: section title(s). All other functions and DOC:
+                       sections are ignored. May be specified multiple times.
+  -nofunction NAME     Do NOT output documentation for the given function(s);
+                       only output documentation for the other functions and
+                       DOC: sections. May be specified multiple times.
+
+Output selection modifiers:
+  -no-doc-sections     Do not output DOC: sections.
+
+Other parameters:
+  -v                   Verbose output, more warnings and other information.
+  -h                   Print this help.
+
+EOF
+    print $message;
+    exit 1;
+}
 
 #
 # format of comments.
@@ -201,6 +204,8 @@ my $type_param = '\@(\w+)';
 my $type_struct = '\&((struct\s*)*[_\w]+)';
 my $type_struct_xml = '\\&amp;((struct\s*)*[_\w]+)';
 my $type_env = '(\$\w+)';
+my $type_enum_full = '\&(enum)\s*([_\w]+)';
+my $type_struct_full = '\&(struct)\s*([_\w]+)';
 
 # Output conversion substitutions.
 #  One for each output format
@@ -266,6 +271,17 @@ my @highlights_text = (
                      );
 my $blankline_text = "";
 
+# rst-mode
+my @highlights_rst = (
+                       [$type_constant, "``\$1``"],
+                       [$type_func, "\\:c\\:func\\:`\$1`"],
+                       [$type_struct_full, "\\:c\\:type\\:`\$1 \$2 <\$2>`"],
+                       [$type_enum_full, "\\:c\\:type\\:`\$1 \$2 <\$2>`"],
+                       [$type_struct, "\\:c\\:type\\:`struct \$1 <\$1>`"],
+                       [$type_param, "**\$1**"]
+                     );
+my $blankline_rst = "\n";
+
 # list mode
 my @highlights_list = (
                        [$type_constant, "\$1"],
@@ -402,6 +418,10 @@ while ($ARGV[0] =~ m/^-(.*)/) {
        $output_mode = "text";
        @highlights = @highlights_text;
        $blankline = $blankline_text;
+    } elsif ($cmd eq "-rst") {
+       $output_mode = "rst";
+       @highlights = @highlights_rst;
+       $blankline = $blankline_rst;
     } elsif ($cmd eq "-docbook") {
        $output_mode = "xml";
        @highlights = @highlights_xml;
@@ -437,17 +457,6 @@ while ($ARGV[0] =~ m/^-(.*)/) {
 
 # continue execution near EOF;
 
-sub usage {
-    print "Usage: $0 [ -docbook | -html | -html5 | -text | -man | -list ]\n";
-    print "         [ -no-doc-sections ]\n";
-    print "         [ -function funcname [ -function funcname ...] ]\n";
-    print "         [ -nofunction funcname [ -nofunction funcname ...] ]\n";
-    print "         [ -v ]\n";
-    print "         c source file(s) > outputfile\n";
-    print "         -v : verbose output, more warnings & other info listed\n";
-    exit 1;
-}
-
 # get kernel version from env
 sub get_kernel_version() {
     my $version = 'unknown kernel version';
@@ -1713,6 +1722,208 @@ sub output_blockhead_text(%) {
     }
 }
 
+##
+# output in restructured text
+#
+
+#
+# This could use some work; it's used to output the DOC: sections, and
+# starts by putting out the name of the doc section itself, but that tends
+# to duplicate a header already in the template file.
+#
+sub output_blockhead_rst(%) {
+    my %args = %{$_[0]};
+    my ($parameter, $section);
+
+    foreach $section (@{$args{'sectionlist'}}) {
+       print "**$section**\n\n";
+       output_highlight_rst($args{'sections'}{$section});
+       print "\n";
+    }
+}
+
+sub output_highlight_rst {
+    my $contents = join "\n",@_;
+    my $line;
+
+    # undo the evil effects of xml_escape() earlier
+    $contents = xml_unescape($contents);
+
+    eval $dohighlight;
+    die $@ if $@;
+
+    foreach $line (split "\n", $contents) {
+       if ($line eq "") {
+           print $lineprefix, $blankline;
+       } else {
+           $line =~ s/\\\\\\/\&/g;
+           print $lineprefix, $line;
+       }
+       print "\n";
+    }
+}
+
+sub output_function_rst(%) {
+    my %args = %{$_[0]};
+    my ($parameter, $section);
+    my $start;
+
+    print ".. c:function:: ";
+    if ($args{'functiontype'} ne "") {
+       $start = $args{'functiontype'} . " " . $args{'function'} . " (";
+    } else {
+       $start = $args{'function'} . " (";
+    }
+    print $start;
+
+    my $count = 0;
+    foreach my $parameter (@{$args{'parameterlist'}}) {
+       if ($count ne 0) {
+           print ", ";
+       }
+       $count++;
+       $type = $args{'parametertypes'}{$parameter};
+       if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) {
+           # pointer-to-function
+           print $1 . $parameter . ") (" . $2;
+       } else {
+           print $type . " " . $parameter;
+       }
+    }
+    print ")\n\n    " . $args{'purpose'} . "\n\n";
+
+    print ":Parameters:\n\n";
+    foreach $parameter (@{$args{'parameterlist'}}) {
+       my $parameter_name = $parameter;
+       #$parameter_name =~ s/\[.*//;
+       $type = $args{'parametertypes'}{$parameter};
+
+       if ($type ne "") {
+           print "      ``$type $parameter``\n";
+       } else {
+           print "      ``$parameter``\n";
+       }
+       if ($args{'parameterdescs'}{$parameter_name} ne $undescribed) {
+           my $oldprefix = $lineprefix;
+           $lineprefix = "        ";
+           output_highlight_rst($args{'parameterdescs'}{$parameter_name});
+           $lineprefix = $oldprefix;
+       } else {
+           print "\n        _undescribed_\n";
+       }
+       print "\n";
+    }
+    output_section_rst(@_);
+}
+
+sub output_section_rst(%) {
+    my %args = %{$_[0]};
+    my $section;
+    my $oldprefix = $lineprefix;
+    $lineprefix = "        ";
+
+    foreach $section (@{$args{'sectionlist'}}) {
+       print ":$section:\n\n";
+       output_highlight_rst($args{'sections'}{$section});
+       print "\n";
+    }
+    print "\n";
+    $lineprefix = $oldprefix;
+}
+
+sub output_enum_rst(%) {
+    my %args = %{$_[0]};
+    my ($parameter);
+    my $count;
+    my $name = "enum " . $args{'enum'};
+
+    print "\n\n.. c:type:: " . $name . "\n\n";
+    print "    " . $args{'purpose'} . "\n\n";
+
+    print "..\n\n:Constants:\n\n";
+    my $oldprefix = $lineprefix;
+    $lineprefix = "    ";
+    foreach $parameter (@{$args{'parameterlist'}}) {
+       print "  `$parameter`\n";
+       if ($args{'parameterdescs'}{$parameter} ne $undescribed) {
+           output_highlight_rst($args{'parameterdescs'}{$parameter});
+       } else {
+           print "    undescribed\n";
+       }
+       print "\n";
+    }
+    $lineprefix = $oldprefix;
+    output_section_rst(@_);
+}
+
+sub output_typedef_rst(%) {
+    my %args = %{$_[0]};
+    my ($parameter);
+    my $count;
+    my $name = "typedef " . $args{'typedef'};
+
+    ### FIXME: should the name below contain "typedef" or not?
+    print "\n\n.. c:type:: " . $name . "\n\n";
+    print "    " . $args{'purpose'} . "\n\n";
+
+    output_section_rst(@_);
+}
+
+sub output_struct_rst(%) {
+    my %args = %{$_[0]};
+    my ($parameter);
+    my $name = $args{'type'} . " " . $args{'struct'};
+
+    print "\n\n.. c:type:: " . $name . "\n\n";
+    print "    " . $args{'purpose'} . "\n\n";
+
+    print ":Definition:\n\n";
+    print " ::\n\n";
+    print "  " . $args{'type'} . " " . $args{'struct'} . " {\n";
+    foreach $parameter (@{$args{'parameterlist'}}) {
+       if ($parameter =~ /^#/) {
+           print "    " . "$parameter\n";
+           next;
+       }
+
+       my $parameter_name = $parameter;
+       $parameter_name =~ s/\[.*//;
+
+       ($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next;
+       $type = $args{'parametertypes'}{$parameter};
+       if ($type =~ m/([^\(]*\(\*)\s*\)\s*\(([^\)]*)\)/) {
+           # pointer-to-function
+           print "    $1 $parameter) ($2);\n";
+       } elsif ($type =~ m/^(.*?)\s*(:.*)/) {
+           # bitfield
+           print "    $1 $parameter$2;\n";
+       } else {
+           print "    " . $type . " " . $parameter . ";\n";
+       }
+    }
+    print "  };\n\n";
+
+    print ":Members:\n\n";
+    foreach $parameter (@{$args{'parameterlist'}}) {
+       ($parameter =~ /^#/) && next;
+
+       my $parameter_name = $parameter;
+       $parameter_name =~ s/\[.*//;
+
+       ($args{'parameterdescs'}{$parameter_name} ne $undescribed) || next;
+       $type = $args{'parametertypes'}{$parameter};
+       print "      `$type $parameter`" . "\n";
+       my $oldprefix = $lineprefix;
+       $lineprefix = "        ";
+       output_highlight_rst($args{'parameterdescs'}{$parameter_name});
+       $lineprefix = $oldprefix;
+       print "\n";
+    }
+    print "\n";
+    output_section_rst(@_);
+}
+
+
 ## list mode output functions
 
 sub output_function_list(%) {
@@ -2414,6 +2625,18 @@ sub xml_escape($) {
        return $text;
 }
 
+# xml_unescape: reverse the effects of xml_escape
+sub xml_unescape($) {
+       my $text = shift;
+       if (($output_mode eq "text") || ($output_mode eq "man")) {
+               return $text;
+       }
+       $text =~ s/\\\\\\amp;/\&/g;
+       $text =~ s/\\\\\\lt;/</g;
+       $text =~ s/\\\\\\gt;/>/g;
+       return $text;
+}
+
 # convert local escape strings to html
 # local escape strings look like:  '\\\\menmonic:' (that's 4 backslashes)
 sub local_unescape($) {