Commit 022eedee authored by Jonathan Corbet's avatar Jonathan Corbet

Merge branch 'jani-rest' into docs-next

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.
parents b79ef07d 0e95abf9
...@@ -42,8 +42,10 @@ ...@@ -42,8 +42,10 @@
#include <unistd.h> #include <unistd.h>
#include <limits.h> #include <limits.h>
#include <errno.h> #include <errno.h>
#include <getopt.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/wait.h> #include <sys/wait.h>
#include <time.h>
/* exitstatus is used to keep track of any failing calls to kernel-doc, /* exitstatus is used to keep track of any failing calls to kernel-doc,
* but execution continues. */ * but execution continues. */
...@@ -68,12 +70,23 @@ FILELINE * docsection; ...@@ -68,12 +70,23 @@ FILELINE * docsection;
#define KERNELDOCPATH "scripts/" #define KERNELDOCPATH "scripts/"
#define KERNELDOC "kernel-doc" #define KERNELDOC "kernel-doc"
#define DOCBOOK "-docbook" #define DOCBOOK "-docbook"
#define RST "-rst"
#define LIST "-list" #define LIST "-list"
#define FUNCTION "-function" #define FUNCTION "-function"
#define NOFUNCTION "-nofunction" #define NOFUNCTION "-nofunction"
#define NODOCSECTIONS "-no-doc-sections" #define NODOCSECTIONS "-no-doc-sections"
#define SHOWNOTFOUND "-show-not-found" #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 *srctree, *kernsrctree;
static char **all_list = NULL; static char **all_list = NULL;
...@@ -95,7 +108,7 @@ static void consume_symbol(const char *sym) ...@@ -95,7 +108,7 @@ static void consume_symbol(const char *sym)
static void usage (void) 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, "Input is read from file.tmpl. Output is sent to stdout\n");
fprintf(stderr, "doc: frontend when generating kernel documentation\n"); fprintf(stderr, "doc: frontend when generating kernel documentation\n");
fprintf(stderr, "depend: generate list of files referenced within file\n"); fprintf(stderr, "depend: generate list of files referenced within file\n");
...@@ -242,7 +255,7 @@ static void find_export_symbols(char * filename) ...@@ -242,7 +255,7 @@ static void find_export_symbols(char * filename)
/* /*
* Document all external or internal functions in a file. * Document all external or internal functions in a file.
* Call kernel-doc with following parameters: * 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 * Function names are obtained from all the src files
* by find_export_symbols. * by find_export_symbols.
* intfunc uses -nofunction * intfunc uses -nofunction
...@@ -263,7 +276,7 @@ static void docfunctions(char * filename, char * type) ...@@ -263,7 +276,7 @@ static void docfunctions(char * filename, char * type)
exit(1); exit(1);
} }
vec[idx++] = KERNELDOC; vec[idx++] = KERNELDOC;
vec[idx++] = DOCBOOK; vec[idx++] = KERNELDOC_FORMAT;
vec[idx++] = NODOCSECTIONS; vec[idx++] = NODOCSECTIONS;
for (i=0; i < symfilecnt; i++) { for (i=0; i < symfilecnt; i++) {
struct symfile * sym = &symfilelist[i]; struct symfile * sym = &symfilelist[i];
...@@ -275,6 +288,9 @@ static void docfunctions(char * filename, char * type) ...@@ -275,6 +288,9 @@ static void docfunctions(char * filename, char * type)
} }
vec[idx++] = filename; vec[idx++] = filename;
vec[idx] = NULL; vec[idx] = NULL;
if (file_format == FORMAT_RST)
printf(".. %s\n", filename);
else
printf("<!-- %s -->\n", filename); printf("<!-- %s -->\n", filename);
exec_kernel_doc(vec); exec_kernel_doc(vec);
fflush(stdout); fflush(stdout);
...@@ -294,7 +310,7 @@ static void singfunc(char * filename, char * line) ...@@ -294,7 +310,7 @@ static void singfunc(char * filename, char * line)
int i, idx = 0; int i, idx = 0;
int startofsym = 1; int startofsym = 1;
vec[idx++] = KERNELDOC; vec[idx++] = KERNELDOC;
vec[idx++] = DOCBOOK; vec[idx++] = KERNELDOC_FORMAT;
vec[idx++] = SHOWNOTFOUND; vec[idx++] = SHOWNOTFOUND;
/* Split line up in individual parameters preceded by FUNCTION */ /* Split line up in individual parameters preceded by FUNCTION */
...@@ -343,7 +359,7 @@ static void docsect(char *filename, char *line) ...@@ -343,7 +359,7 @@ static void docsect(char *filename, char *line)
free(s); free(s);
vec[0] = KERNELDOC; vec[0] = KERNELDOC;
vec[1] = DOCBOOK; vec[1] = KERNELDOC_FORMAT;
vec[2] = SHOWNOTFOUND; vec[2] = SHOWNOTFOUND;
vec[3] = FUNCTION; vec[3] = FUNCTION;
vec[4] = line; vec[4] = line;
...@@ -430,6 +446,32 @@ static void find_all_symbols(char *filename) ...@@ -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: * Parse file, calling action specific functions for:
* 1) Lines containing !E * 1) Lines containing !E
...@@ -443,63 +485,75 @@ static void find_all_symbols(char *filename) ...@@ -443,63 +485,75 @@ static void find_all_symbols(char *filename)
static void parse_file(FILE *infile) static void parse_file(FILE *infile)
{ {
char line[MAXLINESZ]; char line[MAXLINESZ];
char * s; char *p, *s;
while (fgets(line, MAXLINESZ, infile)) { while (fgets(line, MAXLINESZ, infile)) {
if (line[0] == '!') { p = is_directive(line);
s = line + 2; if (!p) {
switch (line[1]) { defaultline(line);
continue;
}
switch (*p++) {
case 'E': case 'E':
while (*s && !isspace(*s)) s++; chomp(p);
*s = '\0'; externalfunctions(p);
externalfunctions(line+2);
break; break;
case 'I': case 'I':
while (*s && !isspace(*s)) s++; chomp(p);
*s = '\0'; internalfunctions(p);
internalfunctions(line+2);
break; break;
case 'D': case 'D':
while (*s && !isspace(*s)) s++; chomp(p);
*s = '\0'; symbolsonly(p);
symbolsonly(line+2);
break; break;
case 'F': case 'F':
/* filename */ /* filename */
while (*s && !isspace(*s)) s++; s = chomp(p);
*s++ = '\0';
/* function names */ /* function names */
while (isspace(*s)) while (isspace(*s))
s++; s++;
singlefunctions(line +2, s); singlefunctions(p, s);
break; break;
case 'P': case 'P':
/* filename */ /* filename */
while (*s && !isspace(*s)) s++; s = chomp(p);
*s++ = '\0';
/* DOC: section name */ /* DOC: section name */
while (isspace(*s)) while (isspace(*s))
s++; s++;
docsection(line + 2, s); docsection(p, s);
break; break;
case 'C': case 'C':
while (*s && !isspace(*s)) s++; chomp(p);
*s = '\0';
if (findall) if (findall)
findall(line+2); findall(p);
break; break;
default: default:
defaultline(line); defaultline(line);
} }
} else {
defaultline(line);
}
} }
fflush(stdout); 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[]) int main(int argc, char *argv[])
{ {
const char *subcommand, *filename;
FILE * infile; FILE * infile;
int i; int i;
...@@ -509,19 +563,66 @@ int main(int argc, char *argv[]) ...@@ -509,19 +563,66 @@ int main(int argc, char *argv[])
kernsrctree = getenv("KBUILD_SRC"); kernsrctree = getenv("KBUILD_SRC");
if (!kernsrctree || !*kernsrctree) if (!kernsrctree || !*kernsrctree)
kernsrctree = srctree; 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(); usage();
exit(1); 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 */ /* Open file, exit on error */
infile = fopen(argv[2], "r"); infile = fopen(filename, "r");
if (infile == NULL) { if (infile == NULL) {
fprintf(stderr, "docproc: "); fprintf(stderr, "docproc: ");
perror(argv[2]); perror(filename);
exit(2); 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. /* Need to do this in two passes.
* First pass is used to collect all symbols exported * First pass is used to collect all symbols exported
* in the various files; * in the various files;
...@@ -557,10 +658,10 @@ int main(int argc, char *argv[]) ...@@ -557,10 +658,10 @@ int main(int argc, char *argv[])
fprintf(stderr, "Warning: didn't use docs for %s\n", fprintf(stderr, "Warning: didn't use docs for %s\n",
all_list[i]); all_list[i]);
} }
} else if (strcmp("depend", argv[1]) == 0) { } else if (strcmp("depend", subcommand) == 0) {
/* Create first part of dependency chain /* Create first part of dependency chain
* file.tmpl */ * file.tmpl */
printf("%s\t", argv[2]); printf("%s\t", filename);
defaultline = noaction; defaultline = noaction;
internalfunctions = adddep; internalfunctions = adddep;
externalfunctions = adddep; externalfunctions = adddep;
...@@ -571,7 +672,7 @@ int main(int argc, char *argv[]) ...@@ -571,7 +672,7 @@ int main(int argc, char *argv[])
parse_file(infile); parse_file(infile);
printf("\n"); printf("\n");
} else { } else {
fprintf(stderr, "Unknown option: %s\n", argv[1]); fprintf(stderr, "Unknown option: %s\n", subcommand);
exit(1); exit(1);
} }
fclose(infile); fclose(infile);
......
...@@ -39,41 +39,44 @@ use strict; ...@@ -39,41 +39,44 @@ use strict;
# 25/07/2012 - Added support for HTML5 # 25/07/2012 - Added support for HTML5
# -- Dan Luedtke <mail@danrl.de> # -- Dan Luedtke <mail@danrl.de>
# sub usage {
# This will read a 'c' file and scan for embedded comments in the my $message = <<"EOF";
# style of gnome comments (+minor extensions - see below). Usage: $0 [OPTION ...] FILE ...
#
Read C language source or header FILEs, extract embedded documentation comments,
# Note: This only supports 'c'. and print formatted documentation to standard output.
# usage: The documentation comments are identified by "/**" opening comment mark. See
# kernel-doc [ -docbook | -html | -html5 | -text | -man | -list ] Documentation/kernel-doc-nano-HOWTO.txt for the documentation comment syntax.
# [ -no-doc-sections ]
# [ -function funcname [ -function funcname ...] ] Output format selection (mutually exclusive):
# c file(s)s > outputfile -docbook Output DocBook format.
# or -html Output HTML format.
# [ -nofunction funcname [ -function funcname ...] ] -html5 Output HTML5 format.
# c file(s)s > outputfile -list Output symbol list format. This is for use by docproc.
# -man Output troff manual page format. This is the default.
# Set output format using one of -docbook -html -html5 -text or -man. -rst Output reStructuredText format.
# Default is man. -text Output plain text format.
# The -list format is for internal use by docproc.
# Output selection (mutually exclusive):
# -no-doc-sections -function NAME Only output documentation for the given function(s)
# Do not output DOC: sections or DOC: section title(s). All other functions and DOC:
# sections are ignored. May be specified multiple times.
# -function funcname -nofunction NAME Do NOT output documentation for the given function(s);
# If set, then only generate documentation for the given function(s) or only output documentation for the other functions and
# DOC: section titles. All other functions and DOC: sections are ignored. DOC: sections. May be specified multiple times.
#
# -nofunction funcname Output selection modifiers:
# If set, then only generate documentation for the other function(s)/DOC: -no-doc-sections Do not output DOC: sections.
# sections. Cannot be used together with -function (yes, that's a bug --
# perl hackers can fix it 8)) Other parameters:
# -v Verbose output, more warnings and other information.
# c files - list of 'c' files to process -h Print this help.
#
# All output goes to stdout, with errors to stderr. EOF
print $message;
exit 1;
}
# #
# format of comments. # format of comments.
...@@ -201,6 +204,8 @@ my $type_param = '\@(\w+)'; ...@@ -201,6 +204,8 @@ my $type_param = '\@(\w+)';
my $type_struct = '\&((struct\s*)*[_\w]+)'; my $type_struct = '\&((struct\s*)*[_\w]+)';
my $type_struct_xml = '\\&amp;((struct\s*)*[_\w]+)'; my $type_struct_xml = '\\&amp;((struct\s*)*[_\w]+)';
my $type_env = '(\$\w+)'; my $type_env = '(\$\w+)';
my $type_enum_full = '\&(enum)\s*([_\w]+)';
my $type_struct_full = '\&(struct)\s*([_\w]+)';
# Output conversion substitutions. # Output conversion substitutions.
# One for each output format # One for each output format
...@@ -266,6 +271,17 @@ my @highlights_text = ( ...@@ -266,6 +271,17 @@ my @highlights_text = (
); );
my $blankline_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 # list mode
my @highlights_list = ( my @highlights_list = (
[$type_constant, "\$1"], [$type_constant, "\$1"],
...@@ -402,6 +418,10 @@ while ($ARGV[0] =~ m/^-(.*)/) { ...@@ -402,6 +418,10 @@ while ($ARGV[0] =~ m/^-(.*)/) {
$output_mode = "text"; $output_mode = "text";
@highlights = @highlights_text; @highlights = @highlights_text;
$blankline = $blankline_text; $blankline = $blankline_text;
} elsif ($cmd eq "-rst") {
$output_mode = "rst";
@highlights = @highlights_rst;
$blankline = $blankline_rst;
} elsif ($cmd eq "-docbook") { } elsif ($cmd eq "-docbook") {
$output_mode = "xml"; $output_mode = "xml";
@highlights = @highlights_xml; @highlights = @highlights_xml;
...@@ -437,17 +457,6 @@ while ($ARGV[0] =~ m/^-(.*)/) { ...@@ -437,17 +457,6 @@ while ($ARGV[0] =~ m/^-(.*)/) {
# continue execution near EOF; # 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 # get kernel version from env
sub get_kernel_version() { sub get_kernel_version() {
my $version = 'unknown kernel version'; my $version = 'unknown kernel version';
...@@ -1713,6 +1722,208 @@ sub output_blockhead_text(%) { ...@@ -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 ## list mode output functions
sub output_function_list(%) { sub output_function_list(%) {
...@@ -2414,6 +2625,18 @@ sub xml_escape($) { ...@@ -2414,6 +2625,18 @@ sub xml_escape($) {
return $text; 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 # convert local escape strings to html
# local escape strings look like: '\\\\menmonic:' (that's 4 backslashes) # local escape strings look like: '\\\\menmonic:' (that's 4 backslashes)
sub local_unescape($) { sub local_unescape($) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment