diff options
Diffstat (limited to 'scripts')
| -rwxr-xr-x | scripts/checkpatch.pl | 14 | ||||
| -rw-r--r-- | scripts/coverity-scan/coverity-scan.docker | 1 | ||||
| -rw-r--r-- | scripts/git.orderfile | 1 | ||||
| -rw-r--r-- | scripts/mtest2make.py | 19 | ||||
| -rwxr-xr-x | scripts/ninjatool.py | 3 | ||||
| -rw-r--r-- | scripts/qapi-gen.py | 2 | ||||
| -rw-r--r-- | scripts/qapi/doc.py | 301 | ||||
| -rw-r--r-- | scripts/qapi/gen.py | 7 | ||||
| -rw-r--r-- | scripts/qapi/parser.py | 105 | ||||
| -rwxr-xr-x | scripts/texi2pod.pl | 536 |
10 files changed, 106 insertions, 883 deletions
diff --git a/scripts/checkpatch.pl b/scripts/checkpatch.pl index 50910899f2..6ed34970f9 100755 --- a/scripts/checkpatch.pl +++ b/scripts/checkpatch.pl @@ -1659,7 +1659,7 @@ sub process { # tabs are only allowed in assembly source code, and in # some scripts we imported from other projects. next if ($realfile =~ /\.(s|S)$/); - next if ($realfile =~ /(checkpatch|get_maintainer|texi2pod)\.pl$/); + next if ($realfile =~ /(checkpatch|get_maintainer)\.pl$/); if ($rawline =~ /^\+.*\t/) { my $herevet = "$here\n" . cat_vet($rawline) . "\n"; @@ -2880,14 +2880,20 @@ sub process { $herecurr); } -# check for %L{u,d,i} in strings +# format strings checks my $string; while ($line =~ /(?:^|")([X\t]*)(?:"|$)/g) { $string = substr($rawline, $-[1], $+[1] - $-[1]); $string =~ s/%%/__/g; + # check for %L{u,d,i} in strings if ($string =~ /(?<!%)%L[udi]/) { ERROR("\%Ld/%Lu are not-standard C, use %lld/%llu\n" . $herecurr); - last; + } + # check for %# or %0# in printf-style format strings + if ($string =~ /(?<!%)%0?#/) { + ERROR("Don't use '#' flag of printf format " . + "('%#') in format strings, use '0x' " . + "prefix instead\n" . $herecurr); } } @@ -3005,7 +3011,7 @@ sub process { return 1; } - if (!$is_patch) { + if (!$is_patch && $filename !~ /cover-letter\.patch$/) { ERROR("Does not appear to be a unified-diff format patch\n"); } diff --git a/scripts/coverity-scan/coverity-scan.docker b/scripts/coverity-scan/coverity-scan.docker index 018c03de6d..501ac67233 100644 --- a/scripts/coverity-scan/coverity-scan.docker +++ b/scripts/coverity-scan/coverity-scan.docker @@ -110,7 +110,6 @@ ENV PACKAGES \ systemd-devel \ systemtap-sdt-devel \ tar \ - texinfo \ usbredir-devel \ virglrenderer-devel \ vte291-devel \ diff --git a/scripts/git.orderfile b/scripts/git.orderfile index a021afc2d5..3736c1d6af 100644 --- a/scripts/git.orderfile +++ b/scripts/git.orderfile @@ -12,7 +12,6 @@ # Documentation docs/* *.rst -*.texi # build system configure diff --git a/scripts/mtest2make.py b/scripts/mtest2make.py index 9cbb2e374d..c3489a4605 100644 --- a/scripts/mtest2make.py +++ b/scripts/mtest2make.py @@ -5,6 +5,7 @@ # Author: Paolo Bonzini <pbonzini@redhat.com> from collections import defaultdict +import itertools import json import os import shlex @@ -36,7 +37,7 @@ SPEED = quick introspect = json.load(sys.stdin) i = 0 -def process_tests(test, suites): +def process_tests(test, targets, suites): global i env = ' '.join(('%s=%s' % (shlex.quote(k), shlex.quote(v)) for k, v in test['env'].items())) @@ -58,12 +59,19 @@ def process_tests(test, suites): i += 1 if test['workdir'] is not None: print('.test.dir.%d := %s' % (i, shlex.quote(test['workdir']))) + + if 'depends' in test: + deps = (targets.get(x, []) for x in test['depends']) + deps = itertools.chain.from_iterable(deps) + else: + deps = ['all'] + print('.test.name.%d := %s' % (i, test['name'])) print('.test.driver.%d := %s' % (i, driver)) print('.test.env.%d := $(.test.env) %s' % (i, env)) print('.test.cmd.%d := %s' % (i, cmd)) print('.PHONY: run-test-%d' % (i,)) - print('run-test-%d: all' % (i,)) + print('run-test-%d: %s' % (i, ' '.join(deps))) print('\t@$(call .test.run,%d,$(.test.output-format))' % (i,)) test_suites = test['suite'] or ['default'] @@ -102,16 +110,19 @@ def emit_suite(name, suite, prefix): print('.tests += $(.test.$(SPEED).%s)' % (target, )) print('endif') +targets = {t['id']: [os.path.relpath(f) for f in t['filename']] + for t in introspect['targets']} + testsuites = defaultdict(Suite) for test in introspect['tests']: - process_tests(test, testsuites) + process_tests(test, targets, testsuites) emit_prolog(testsuites, 'check') for name, suite in testsuites.items(): emit_suite(name, suite, 'check') benchsuites = defaultdict(Suite) for test in introspect['benchmarks']: - process_tests(test, benchsuites) + process_tests(test, targets, benchsuites) emit_prolog(benchsuites, 'bench') for name, suite in benchsuites.items(): emit_suite(name, suite, 'bench') diff --git a/scripts/ninjatool.py b/scripts/ninjatool.py index 627a1cab45..6f0e35c727 100755 --- a/scripts/ninjatool.py +++ b/scripts/ninjatool.py @@ -908,6 +908,9 @@ class Ninja2Make(NinjaParserEventsWithVars): else: stamp = '%s@%s.stamp' % (rule, sha1_text(targets)[0:11]) self.print('%s: %s; @:' % (targets, stamp)) + self.print('ifneq (%s, $(wildcard %s))' % (targets, targets)) + self.print('.PHONY: %s' % (stamp, )) + self.print('endif') self.print('%s: %s | %s; ${ninja-command-restat}' % (stamp, inputs, orderonly)) self.rule_targets[rule].append(stamp) self.stamp_targets[rule].append(stamp) diff --git a/scripts/qapi-gen.py b/scripts/qapi-gen.py index 4b03f7d53b..541e8c1f55 100644 --- a/scripts/qapi-gen.py +++ b/scripts/qapi-gen.py @@ -10,7 +10,6 @@ import re import sys from qapi.commands import gen_commands -from qapi.doc import gen_doc from qapi.events import gen_events from qapi.introspect import gen_introspect from qapi.schema import QAPIError, QAPISchema @@ -51,7 +50,6 @@ def main(argv): gen_commands(schema, args.output_dir, args.prefix) gen_events(schema, args.output_dir, args.prefix) gen_introspect(schema, args.output_dir, args.prefix, args.unmask) - gen_doc(schema, args.output_dir, args.prefix) if __name__ == '__main__': diff --git a/scripts/qapi/doc.py b/scripts/qapi/doc.py deleted file mode 100644 index 92f584edcf..0000000000 --- a/scripts/qapi/doc.py +++ /dev/null @@ -1,301 +0,0 @@ -# QAPI texi generator -# -# This work is licensed under the terms of the GNU LGPL, version 2+. -# See the COPYING file in the top-level directory. -"""This script produces the documentation of a qapi schema in texinfo format""" - -import re -from qapi.gen import QAPIGenDoc, QAPISchemaVisitor - - -MSG_FMT = """ -@deftypefn {type} {{}} {name} - -{body}{members}{features}{sections} -@end deftypefn - -""".format - -TYPE_FMT = """ -@deftp {{{type}}} {name} - -{body}{members}{features}{sections} -@end deftp - -""".format - -EXAMPLE_FMT = """@example -{code} -@end example -""".format - - -def subst_strong(doc): - """Replaces *foo* by @strong{foo}""" - return re.sub(r'\*([^*\n]+)\*', r'@strong{\1}', doc) - - -def subst_emph(doc): - """Replaces _foo_ by @emph{foo}""" - return re.sub(r'\b_([^_\n]+)_\b', r'@emph{\1}', doc) - - -def subst_vars(doc): - """Replaces @var by @code{var}""" - return re.sub(r'@([\w-]+)', r'@code{\1}', doc) - - -def subst_braces(doc): - """Replaces {} with @{ @}""" - return doc.replace('{', '@{').replace('}', '@}') - - -def texi_example(doc): - """Format @example""" - # TODO: Neglects to escape @ characters. - # We should probably escape them in subst_braces(), and rename the - # function to subst_special() or subs_texi_special(). If we do that, we - # need to delay it until after subst_vars() in texi_format(). - doc = subst_braces(doc).strip('\n') - return EXAMPLE_FMT(code=doc) - - -def texi_format(doc): - """ - Format documentation - - Lines starting with: - - |: generates an @example - - =: generates @section - - ==: generates @subsection - - 1. or 1): generates an @enumerate @item - - */-: generates an @itemize list - """ - ret = '' - doc = subst_braces(doc) - doc = subst_vars(doc) - doc = subst_emph(doc) - doc = subst_strong(doc) - inlist = '' - lastempty = False - for line in doc.split('\n'): - empty = line == '' - - # FIXME: Doing this in a single if / elif chain is - # problematic. For instance, a line without markup terminates - # a list if it follows a blank line (reaches the final elif), - # but a line with some *other* markup, such as a = title - # doesn't. - # - # Make sure to update section "Documentation markup" in - # docs/devel/qapi-code-gen.txt when fixing this. - if line.startswith('| '): - line = EXAMPLE_FMT(code=line[2:]) - elif line.startswith('= '): - line = '@section ' + line[2:] - elif line.startswith('== '): - line = '@subsection ' + line[3:] - elif re.match(r'^([0-9]*\.) ', line): - if not inlist: - ret += '@enumerate\n' - inlist = 'enumerate' - ret += '@item\n' - line = line[line.find(' ')+1:] - elif re.match(r'^[*-] ', line): - if not inlist: - ret += '@itemize %s\n' % {'*': '@bullet', - '-': '@minus'}[line[0]] - inlist = 'itemize' - ret += '@item\n' - line = line[2:] - elif lastempty and inlist: - ret += '@end %s\n\n' % inlist - inlist = '' - - lastempty = empty - ret += line + '\n' - - if inlist: - ret += '@end %s\n\n' % inlist - return ret - - -def texi_body(doc): - """Format the main documentation body""" - return texi_format(doc.body.text) - - -def texi_if(ifcond, prefix='\n', suffix='\n'): - """Format the #if condition""" - if not ifcond: - return '' - return '%s@b{If:} @code{%s}%s' % (prefix, ', '.join(ifcond), suffix) - - -def texi_enum_value(value, desc, suffix): - """Format a table of members item for an enumeration value""" - return '@item @code{%s}\n%s%s' % ( - value.name, desc, texi_if(value.ifcond, prefix='@*')) - - -def texi_member(member, desc, suffix): - """Format a table of members item for an object type member""" - typ = member.type.doc_type() - membertype = ': ' + typ if typ else '' - return '@item @code{%s%s}%s%s\n%s%s' % ( - member.name, membertype, - ' (optional)' if member.optional else '', - suffix, desc, texi_if(member.ifcond, prefix='@*')) - - -def texi_members(doc, what, base=None, variants=None, - member_func=texi_member): - """Format the table of members""" - items = '' - for section in doc.args.values(): - # TODO Drop fallbacks when undocumented members are outlawed - if section.text: - desc = texi_format(section.text) - elif (variants and variants.tag_member == section.member - and not section.member.type.doc_type()): - values = section.member.type.member_names() - members_text = ', '.join(['@t{"%s"}' % v for v in values]) - desc = 'One of ' + members_text + '\n' - else: - desc = 'Not documented\n' - items += member_func(section.member, desc, suffix='') - if base: - items += '@item The members of @code{%s}\n' % base.doc_type() - if variants: - for v in variants.variants: - when = ' when @code{%s} is @t{"%s"}%s' % ( - variants.tag_member.name, v.name, texi_if(v.ifcond, " (", ")")) - if v.type.is_implicit(): - assert not v.type.base and not v.type.variants - for m in v.type.local_members: - items += member_func(m, desc='', suffix=when) - else: - items += '@item The members of @code{%s}%s\n' % ( - v.type.doc_type(), when) - if not items: - return '' - return '\n@b{%s:}\n@table @asis\n%s@end table\n' % (what, items) - - -def texi_arguments(doc, boxed_arg_type): - if boxed_arg_type: - assert not doc.args - return ('\n@b{Arguments:} the members of @code{%s}\n' - % boxed_arg_type.name) - return texi_members(doc, 'Arguments') - - -def texi_features(doc): - """Format the table of features""" - items = '' - for section in doc.features.values(): - desc = texi_format(section.text) - items += '@item @code{%s}\n%s' % (section.name, desc) - if not items: - return '' - return '\n@b{Features:}\n@table @asis\n%s@end table\n' % (items) - - -def texi_sections(doc, ifcond): - """Format additional sections following arguments""" - body = '' - for section in doc.sections: - if section.name: - # prefer @b over @strong, so txt doesn't translate it to *Foo:* - body += '\n@b{%s:}\n' % section.name - if section.name and section.name.startswith('Example'): - body += texi_example(section.text) - else: - body += texi_format(section.text) - body += texi_if(ifcond, suffix='') - return body - - -def texi_type(typ, doc, ifcond, members): - return TYPE_FMT(type=typ, - name=doc.symbol, - body=texi_body(doc), - members=members, - features=texi_features(doc), - sections=texi_sections(doc, ifcond)) - - -def texi_msg(typ, doc, ifcond, members): - return MSG_FMT(type=typ, - name=doc.symbol, - body=texi_body(doc), - members=members, - features=texi_features(doc), - sections=texi_sections(doc, ifcond)) - - -class QAPISchemaGenDocVisitor(QAPISchemaVisitor): - def __init__(self, prefix): - self._prefix = prefix - self._gen = QAPIGenDoc(self._prefix + 'qapi-doc.texi') - self.cur_doc = None - - def write(self, output_dir): - self._gen.write(output_dir) - - def visit_enum_type(self, name, info, ifcond, features, members, prefix): - doc = self.cur_doc - self._gen.add(texi_type('Enum', doc, ifcond, - texi_members(doc, 'Values', - member_func=texi_enum_value))) - - def visit_object_type(self, name, info, ifcond, features, - base, members, variants): - doc = self.cur_doc - if base and base.is_implicit(): - base = None - self._gen.add(texi_type('Object', doc, ifcond, - texi_members(doc, 'Members', base, variants))) - - def visit_alternate_type(self, name, info, ifcond, features, variants): - doc = self.cur_doc - self._gen.add(texi_type('Alternate', doc, ifcond, - texi_members(doc, 'Members'))) - - def visit_command(self, name, info, ifcond, features, - arg_type, ret_type, gen, success_response, boxed, - allow_oob, allow_preconfig): - doc = self.cur_doc - self._gen.add(texi_msg('Command', doc, ifcond, - texi_arguments(doc, - arg_type if boxed else None))) - - def visit_event(self, name, info, ifcond, features, arg_type, boxed): - doc = self.cur_doc - self._gen.add(texi_msg('Event', doc, ifcond, - texi_arguments(doc, - arg_type if boxed else None))) - - def symbol(self, doc, entity): - if self._gen._body: - self._gen.add('\n') - self.cur_doc = doc - entity.visit(self) - self.cur_doc = None - - def freeform(self, doc): - assert not doc.args - if self._gen._body: - self._gen.add('\n') - self._gen.add(texi_body(doc) + texi_sections(doc, None)) - - -def gen_doc(schema, output_dir, prefix): - vis = QAPISchemaGenDocVisitor(prefix) - vis.visit_begin(schema) - for doc in schema.docs: - if doc.symbol: - vis.symbol(doc, schema.lookup_entity(doc.symbol)) - else: - vis.freeform(doc) - vis.write(output_dir) diff --git a/scripts/qapi/gen.py b/scripts/qapi/gen.py index bf5552a4e7..ca66c82b5b 100644 --- a/scripts/qapi/gen.py +++ b/scripts/qapi/gen.py @@ -178,13 +178,6 @@ def ifcontext(ifcond, *args): arg.end_if() -class QAPIGenDoc(QAPIGen): - - def _top(self): - return (super()._top() - + '@c AUTOMATICALLY GENERATED, DO NOT MODIFY\n\n') - - class QAPISchemaMonolithicCVisitor(QAPISchemaVisitor): def __init__(self, prefix, what, blurb, pydoc): diff --git a/scripts/qapi/parser.py b/scripts/qapi/parser.py index 165925ca72..9d1a3e2eea 100644 --- a/scripts/qapi/parser.py +++ b/scripts/qapi/parser.py @@ -319,17 +319,32 @@ class QAPIDoc: """ class Section: - def __init__(self, name=None): + def __init__(self, parser, name=None, indent=0): + # parser, for error messages about indentation + self._parser = parser # optional section name (argument/member or section name) self.name = name self.text = '' + # the expected indent level of the text of this section + self._indent = indent def append(self, line): + # Strip leading spaces corresponding to the expected indent level + # Blank lines are always OK. + if line: + indent = re.match(r'\s*', line).end() + if indent < self._indent: + raise QAPIParseError( + self._parser, + "unexpected de-indent (expected at least %d spaces)" % + self._indent) + line = line[self._indent:] + self.text += line.rstrip() + '\n' class ArgSection(Section): - def __init__(self, name): - super().__init__(name) + def __init__(self, parser, name, indent=0): + super().__init__(parser, name, indent) self.member = None def connect(self, member): @@ -343,7 +358,7 @@ class QAPIDoc: self._parser = parser self.info = info self.symbol = None - self.body = QAPIDoc.Section() + self.body = QAPIDoc.Section(parser) # dict mapping parameter name to ArgSection self.args = OrderedDict() self.features = OrderedDict() @@ -427,10 +442,10 @@ class QAPIDoc: self._append_line = self._append_various_line self._append_various_line(line) else: - self._append_freeform(line.strip()) + self._append_freeform(line) else: # This is a free-form documentation block - self._append_freeform(line.strip()) + self._append_freeform(line) def _append_args_line(self, line): """ @@ -447,8 +462,21 @@ class QAPIDoc: name = line.split(' ', 1)[0] if name.startswith('@') and name.endswith(':'): - line = line[len(name)+1:] - self._start_args_section(name[1:-1]) + # If line is "@arg: first line of description", find + # the index of 'f', which is the indent we expect for any + # following lines. We then remove the leading "@arg:" + # from line and replace it with spaces so that 'f' has the + # same index as it did in the original line and can be + # handled the same way we will handle following lines. + indent = re.match(r'@\S*:\s*', line).end() + line = line[indent:] + if not line: + # Line was just the "@arg:" header; following lines + # are not indented + indent = 0 + else: + line = ' ' * indent + line + self._start_args_section(name[1:-1], indent) elif self._is_section_tag(name): self._append_line = self._append_various_line self._append_various_line(line) @@ -463,14 +491,27 @@ class QAPIDoc: self._append_various_line(line) return - self._append_freeform(line.strip()) + self._append_freeform(line) def _append_features_line(self, line): name = line.split(' ', 1)[0] if name.startswith('@') and name.endswith(':'): - line = line[len(name)+1:] - self._start_features_section(name[1:-1]) + # If line is "@arg: first line of description", find + # the index of 'f', which is the indent we expect for any + # following lines. We then remove the leading "@arg:" + # from line and replace it with spaces so that 'f' has the + # same index as it did in the original line and can be + # handled the same way we will handle following lines. + indent = re.match(r'@\S*:\s*', line).end() + line = line[indent:] + if not line: + # Line was just the "@arg:" header; following lines + # are not indented + indent = 0 + else: + line = ' ' * indent + line + self._start_features_section(name[1:-1], indent) elif self._is_section_tag(name): self._append_line = self._append_various_line self._append_various_line(line) @@ -482,7 +523,7 @@ class QAPIDoc: self._append_various_line(line) return - self._append_freeform(line.strip()) + self._append_freeform(line) def _append_various_line(self, line): """ @@ -502,16 +543,25 @@ class QAPIDoc: "'%s' can't follow '%s' section" % (name, self.sections[0].name)) if self._is_section_tag(name): - line = line[len(name)+1:] - self._start_section(name[:-1]) - - if (not self._section.name or - not self._section.name.startswith('Example')): - line = line.strip() + # If line is "Section: first line of description", find + # the index of 'f', which is the indent we expect for any + # following lines. We then remove the leading "Section:" + # from line and replace it with spaces so that 'f' has the + # same index as it did in the original line and can be + # handled the same way we will handle following lines. + indent = re.match(r'\S*:\s*', line).end() + line = line[indent:] + if not line: + # Line was just the "Section:" header; following lines + # are not indented + indent = 0 + else: + line = ' ' * indent + line + self._start_section(name[:-1], indent) self._append_freeform(line) - def _start_symbol_section(self, symbols_dict, name): + def _start_symbol_section(self, symbols_dict, name, indent): # FIXME invalid names other than the empty string aren't flagged if not name: raise QAPIParseError(self._parser, "invalid parameter name") @@ -520,21 +570,21 @@ class QAPIDoc: "'%s' parameter name duplicated" % name) assert not self.sections self._end_section() - self._section = QAPIDoc.ArgSection(name) + self._section = QAPIDoc.ArgSection(self._parser, name, indent) symbols_dict[name] = self._section - def _start_args_section(self, name): - self._start_symbol_section(self.args, name) + def _start_args_section(self, name, indent): + self._start_symbol_section(self.args, name, indent) - def _start_features_section(self, name): - self._start_symbol_section(self.features, name) + def _start_features_section(self, name, indent): + self._start_symbol_section(self.features, name, indent) - def _start_section(self, name=None): + def _start_section(self, name=None, indent=0): if name in ('Returns', 'Since') and self.has_section(name): raise QAPIParseError(self._parser, "duplicated '%s' section" % name) self._end_section() - self._section = QAPIDoc.Section(name) + self._section = QAPIDoc.Section(self._parser, name, indent) self.sections.append(self._section) def _end_section(self): @@ -557,7 +607,8 @@ class QAPIDoc: def connect_member(self, member): if member.name not in self.args: # Undocumented TODO outlaw - self.args[member.name] = QAPIDoc.ArgSection(member.name) + self.args[member.name] = QAPIDoc.ArgSection(self._parser, + member.name) self.args[member.name].connect(member) def connect_feature(self, feature): diff --git a/scripts/texi2pod.pl b/scripts/texi2pod.pl deleted file mode 100755 index 8bfc6f6f4c..0000000000 --- a/scripts/texi2pod.pl +++ /dev/null @@ -1,536 +0,0 @@ -#! /usr/bin/env perl - -# Copyright (C) 1999, 2000, 2001, 2003 Free Software Foundation, Inc. - -# This file is part of GCC. - -# GCC is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2, or (at your option) -# any later version. - -# GCC is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with GCC; see the file COPYING. If not, -# see <http://www.gnu.org/licenses/>. - -# This does trivial (and I mean _trivial_) conversion of Texinfo -# markup to Perl POD format. It's intended to be used to extract -# something suitable for a manpage from a Texinfo document. - -use warnings; - -$output = 0; -$skipping = 0; -%sects = (); -$section = ""; -@icstack = (); -@endwstack = (); -@skstack = (); -@instack = (); -$shift = ""; -%defs = (); -$fnno = 1; -$inf = ""; -$ibase = ""; -@ipath = (); -$encoding = undef; -@args = (); - -while ($_ = shift) { - if (/^-D(.*)$/) { - if ($1 ne "") { - $flag = $1; - } else { - $flag = shift; - } - $value = ""; - ($flag, $value) = ($flag =~ /^([^=]+)(?:=(.+))?/); - die "no flag specified for -D\n" - unless $flag ne ""; - die "flags may only contain letters, digits, hyphens, dashes and underscores\n" - unless $flag =~ /^[a-zA-Z0-9_-]+$/; - $defs{$flag} = $value; - } elsif (/^-I(.*)$/) { - if ($1 ne "") { - $flag = $1; - } else { - $flag = shift; - } - push (@ipath, $flag); - } elsif (/^-/) { - usage(); - } else { - $in = $_, next unless defined $in; - $out = $_, next unless defined $out; - usage(); - } -} - -if (defined $in) { - $inf = gensym(); - open($inf, "<$in") or die "opening \"$in\": $!\n"; - $ibase = $1 if $in =~ m|^(.+)/[^/]+$|; -} else { - $inf = \*STDIN; -} - -if (defined $out) { - open(STDOUT, ">$out") or die "opening \"$out\": $!\n"; -} - -while(defined $inf) { -while(<$inf>) { - # Certain commands are discarded without further processing. - /^\@(?: - [a-z]+index # @*index: useful only in complete manual - |need # @need: useful only in printed manual - |(?:end\s+)?group # @group .. @end group: ditto - |page # @page: ditto - |node # @node: useful only in .info file - |(?:end\s+)?ifnottex # @ifnottex .. @end ifnottex: use contents - )\b/x and next; - - chomp; - - # Look for filename and title markers. - /^\@setfilename\s+([^.]+)/ and $fn = $1, next; - /^\@settitle\s+([^.]+)/ and $tl = postprocess($1), next; - - # Look for document encoding - /^\@documentencoding\s+([^.]+)/ and do { - $encoding = $1 unless defined $encoding; - next; - }; - - # Identify a man title but keep only the one we are interested in. - /^\@c\s+man\s+title\s+([A-Za-z0-9-]+)\s+(.+)/ and do { - if (exists $defs{$1}) { - $fn = $1; - $tl = postprocess($2); - } - next; - }; - - # Look for blocks surrounded by @c man begin SECTION ... @c man end. - # This really oughta be @ifman ... @end ifman and the like, but such - # would require rev'ing all other Texinfo translators. - /^\@c\s+man\s+begin\s+([A-Z]+)\s+([A-Za-z0-9-]+)/ and do { - $output = 1 if exists $defs{$2}; - $sect = $1; - next; - }; - /^\@c\s+man\s+begin\s+([A-Z]+)/ and $sect = $1, $output = 1, next; - /^\@c\s+man\s+end/ and do { - $sects{$sect} = "" unless exists $sects{$sect}; - $sects{$sect} .= postprocess($section); - $section = ""; - $output = 0; - next; - }; - - # handle variables - /^\@set\s+([a-zA-Z0-9_-]+)\s*(.*)$/ and do { - $defs{$1} = $2; - next; - }; - /^\@clear\s+([a-zA-Z0-9_-]+)/ and do { - delete $defs{$1}; - next; - }; - - # Single line command handlers. - - /^\@include\s+(.+)$/ and do { - push @instack, $inf; - $inf = gensym(); - $file = postprocess($1); - - # Try cwd and $ibase, then explicit -I paths. - $done = 0; - foreach $path ("", $ibase, @ipath) { - $mypath = $file; - $mypath = $path . "/" . $mypath if ($path ne ""); - open($inf, "<" . $mypath) and ($done = 1, last); - } - die "cannot find $file" if !$done; - next; - }; - - next unless $output; - - # Discard comments. (Can't do it above, because then we'd never see - # @c man lines.) - /^\@c\b/ and next; - - # End-block handler goes up here because it needs to operate even - # if we are skipping. - /^\@end\s+([a-z]+)/ and do { - # Ignore @end foo, where foo is not an operation which may - # cause us to skip, if we are presently skipping. - my $ended = $1; - next if $skipping && $ended !~ /^(?:ifset|ifclear|ignore|menu|iftex|copying)$/; - - die "\@end $ended without \@$ended at line $.\n" unless defined $endw; - die "\@$endw ended by \@end $ended at line $.\n" unless $ended eq $endw; - - $endw = pop @endwstack; - - if ($ended =~ /^(?:ifset|ifclear|ignore|menu|iftex)$/) { - $skipping = pop @skstack; - next; - } elsif ($ended =~ /^(?:example|smallexample|display - |quotation|deftp|deftypefn)$/x) { - $shift = ""; - $_ = ""; # need a paragraph break - } elsif ($ended =~ /^(?:itemize|enumerate|[fv]?table)$/) { - $_ = "\n=back\n"; - $ic = pop @icstack; - } elsif ($ended eq "multitable") { - $_ = "\n=back\n"; - } else { - die "unknown command \@end $ended at line $.\n"; - } - }; - - # We must handle commands which can cause skipping even while we - # are skipping, otherwise we will not process nested conditionals - # correctly. - /^\@ifset\s+([a-zA-Z0-9_-]+)/ and do { - push @endwstack, $endw; - push @skstack, $skipping; - $endw = "ifset"; - $skipping = 1 unless exists $defs{$1}; - next; - }; - - /^\@ifclear\s+([a-zA-Z0-9_-]+)/ and do { - push @endwstack, $endw; - push @skstack, $skipping; - $endw = "ifclear"; - $skipping = 1 if exists $defs{$1}; - next; - }; - - /^\@(ignore|menu|iftex|copying)\b/ and do { - push @endwstack, $endw; - push @skstack, $skipping; - $endw = $1; - $skipping = 1; - next; - }; - - next if $skipping; - - # Character entities. First the ones that can be replaced by raw text - # or discarded outright: - s/\@copyright\{\}/(c)/g; - s/\@dots\{\}/.../g; - s/\@enddots\{\}/..../g; - s/\@([.!? ])/$1/g; - s/\@[:-]//g; - s/\@bullet(?:\{\})?/*/g; - s/\@TeX\{\}/TeX/g; - s/\@pounds\{\}/\#/g; - s/\@minus(?:\{\})?/-/g; - s/\\,/,/g; - - # Now the ones that have to be replaced by special escapes - # (which will be turned back into text by unmunge()) - s/&/&/g; - s/\@\{/{/g; - s/\@\}/}/g; - s/\@\@/&at;/g; - - # Inside a verbatim block, handle @var specially. - if ($shift ne "") { - s/\@var\{([^\}]*)\}/<$1>/g; - } - - # POD doesn't interpret E<> inside a verbatim block. - if ($shift eq "") { - s/</</g; - s/>/>/g; - } else { - s/</</g; - s/>/>/g; - } - - /^\@(?:section|unnumbered|unnumberedsec|center)\s+(.+)$/ - and $_ = "\n=head2 $1\n"; - /^\@subsection\s+(.+)$/ - and $_ = "\n=head3 $1\n"; - /^\@subsubsection\s+(.+)$/ - and $_ = "\n=head4 $1\n"; - - # Block command handlers: - /^\@itemize(?:\s+(\@[a-z]+|\*|-))?/ and do { - push @endwstack, $endw; - push @icstack, $ic; - if (defined $1) { - $ic = $1; - } else { - $ic = '*'; - } - $_ = "\n=over 4\n"; - $endw = "itemize"; - }; - - /^\@enumerate(?:\s+([a-zA-Z0-9]+))?/ and do { - push @endwstack, $endw; - push @icstack, $ic; - if (defined $1) { - $ic = $1 . "."; - } else { - $ic = "1."; - } - $_ = "\n=over 4\n"; - $endw = "enumerate"; - }; - - /^\@multitable\s.*/ and do { - push @endwstack, $endw; - $endw = "multitable"; - $_ = "\n=over 4\n"; - }; - - /^\@([fv]?table)\s+(\@[a-z]+)/ and do { - push @endwstack, $endw; - push @icstack, $ic; - $endw = $1; - $ic = $2; - $ic =~ s/\@(?:samp|strong|key|gcctabopt|option|env)/B/; - $ic =~ s/\@(?:code|kbd)/C/; - $ic =~ s/\@(?:dfn|var|emph|cite|i)/I/; - $ic =~ s/\@(?:file)/F/; - $ic =~ s/\@(?:asis)//; - $_ = "\n=over 4\n"; - }; - - /^\@((?:small)?example|display)/ and do { - push @endwstack, $endw; - $endw = $1; - $shift = "\t"; - $_ = ""; # need a paragraph break - }; - - /^\@item\s+(.*\S)\s*$/ and $endw eq "multitable" and do { - @columns = (); - for $column (split (/\s*\@tab\s*/, $1)) { - # @strong{...} is used a @headitem work-alike - $column =~ s/^\@strong\{(.*)\}$/$1/; - push @columns, $column; - } - $_ = "\n=item ".join (" : ", @columns)."\n"; - }; - - /^\@(quotation)\s*(.+)?$/ and do { - push @endwstack, $endw; - $endw = $1; - $_ = "\n$2:" - }; - - /^{(.*)}$|^(.*)$/ and $#args > 0 and do { - $kind = $args[0]; - $arguments = $1 // ""; - if ($endw eq "deftypefn") { - $ret = $args[1]; - $fname = "B<$args[2]>"; - $_ = $ret ? "$ret " : ""; - $_ .= "$fname $arguments ($kind)"; - } else { - $_ = "B<$args[1]> ($kind)\n\n$arguments"; - } - @args = (); - }; - - /^\@(deftp)\s*(.+)?$/ and do { - push @endwstack, $endw; - $endw = $1; - $arg = $2; - $arg =~ s/{([^}]*)}/$1/g; - $arg =~ s/\@$//; - @args = split (/ /, $arg); - $_ = ""; - }; - - /^\@(deftypefn)\s*(.+)?$/ and do { - push @endwstack, $endw; - $endw = $1; - $arg = $2; - $arg =~ s/{([^}]*)}/$1/g; - $arg =~ s/\@$//; - @args = split (/ /, $arg); - $_ = ""; - }; - - /^\@itemx?\s*(.+)?$/ and do { - if (defined $1) { - if ($ic eq "") { - $_ = "\n=item $1\n"; - } else { - # Entity escapes prevent munging by the <> processing below. - $_ = "\n=item $ic\<$1\>\n"; - } - } else { - $_ = "\n=item $ic\n"; - $ic =~ y/A-Ya-y/B-Zb-z/; - $ic =~ s/(\d+)/$1 + 1/eg; - } - }; - - $section .= $shift.$_."\n"; -} -# End of current file. -close($inf); -$inf = pop @instack; -} - -die "No filename or title\n" unless defined $fn && defined $tl; - -print "=encoding $encoding\n\n" if defined $encoding; - -$sects{NAME} = "$fn \- $tl\n"; -$sects{FOOTNOTES} .= "=back\n" if exists $sects{FOOTNOTES}; - -for $sect (qw(NAME SYNOPSIS DESCRIPTION OPTIONS ENVIRONMENT FILES - BUGS NOTES FOOTNOTES EXAMPLES SEEALSO AUTHOR COPYRIGHT)) { - if(exists $sects{$sect}) { - $head = $sect; - $head =~ s/SEEALSO/SEE ALSO/; - print "=head1 $head\n\n"; - print scalar unmunge ($sects{$sect}); - print "\n"; - } -} - -sub usage -{ - die "usage: $0 [-D toggle...] [infile [outfile]]\n"; -} - -sub postprocess -{ - local $_ = $_[0]; - - # @value{foo} is replaced by whatever 'foo' is defined as. - while (m/(\@value\{([a-zA-Z0-9_-]+)\})/g) { - if (! exists $defs{$2}) { - print STDERR "Option $2 not defined\n"; - s/\Q$1\E//; - } else { - $value = $defs{$2}; - s/\Q$1\E/$value/; - } - } - - # Formatting commands. - # Temporary escape for @r. - s/\@r\{([^\}]*)\}/R<$1>/g; - s/\@(?:dfn|var|emph|cite|i)\{([^\}]*)\}/I<$1>/g; - s/\@(?:code|kbd)\{([^\}]*)\}/C<$1>/g; - s/\@(?:gccoptlist|samp|strong|key|option|env|command|b)\{([^\}]*)\}/B<$1>/g; - s/\@sc\{([^\}]*)\}/\U$1/g; - s/\@file\{([^\}]*)\}/F<$1>/g; - s/\@w\{([^\}]*)\}/S<$1>/g; - s/\@t\{([^\}]*)\}/$1/g; - s/\@(?:dmn|math)\{([^\}]*)\}/$1/g; - - # keep references of the form @ref{...}, print them bold - s/\@(?:ref)\{([^\}]*)\}/B<$1>/g; - - # Change double single quotes to double quotes. - s/''/"/g; - s/``/"/g; - - # Cross references are thrown away, as are @noindent and @refill. - # (@noindent is impossible in .pod, and @refill is unnecessary.) - # @* is also impossible in .pod; we discard it and any newline that - # follows it. Similarly, our macro @gol must be discarded. - - s/\(?\@xref\{(?:[^\}]*)\}(?:[^.<]|(?:<[^<>]*>))*\.\)?//g; - s/\s+\(\@pxref\{(?:[^\}]*)\}\)//g; - s/;\s+\@pxref\{(?:[^\}]*)\}//g; - s/\@noindent\s*//g; - s/\@refill//g; - s/\@gol//g; - s/\@\*\s*\n?//g; - - # Anchors are thrown away - s/\@anchor\{(?:[^\}]*)\}//g; - - # @uref can take one, two, or three arguments, with different - # semantics each time. @url and @email are just like @uref with - # one argument, for our purposes. - s/\@(?:uref|url|email)\{([^\},]*)\}/<B<$1>>/g; - s/\@uref\{([^\},]*),([^\},]*)\}/$2 (C<$1>)/g; - s/\@uref\{([^\},]*),([^\},]*),([^\},]*)\}/$3/g; - - # Un-escape <> at this point. - s/</</g; - s/>/>/g; - - # Now un-nest all B<>, I<>, R<>. Theoretically we could have - # indefinitely deep nesting; in practice, one level suffices. - 1 while s/([BIR])<([^<>]*)([BIR])<([^<>]*)>/$1<$2>$3<$4>$1</g; - - # Replace R<...> with bare ...; eliminate empty markup, B<>; - # shift white space at the ends of [BI]<...> expressions outside - # the expression. - s/R<([^<>]*)>/$1/g; - s/[BI]<>//g; - s/([BI])<(\s+)([^>]+)>/$2$1<$3>/g; - s/([BI])<([^>]+?)(\s+)>/$1<$2>$3/g; - - # Extract footnotes. This has to be done after all other - # processing because otherwise the regexp will choke on formatting - # inside @footnote. - while (/\@footnote/g) { - s/\@footnote\{([^\}]+)\}/[$fnno]/; - add_footnote($1, $fnno); - $fnno++; - } - - return $_; -} - -sub unmunge -{ - # Replace escaped symbols with their equivalents. - local $_ = $_[0]; - - s/</E<lt>/g; - s/>/E<gt>/g; - s/{/\{/g; - s/}/\}/g; - s/&at;/\@/g; - s/&/&/g; - return $_; -} - -sub add_footnote -{ - unless (exists $sects{FOOTNOTES}) { - $sects{FOOTNOTES} = "\n=over 4\n\n"; - } - - $sects{FOOTNOTES} .= "=item $fnno.\n\n"; $fnno++; - $sects{FOOTNOTES} .= $_[0]; - $sects{FOOTNOTES} .= "\n\n"; -} - -# stolen from Symbol.pm -{ - my $genseq = 0; - sub gensym - { - my $name = "GEN" . $genseq++; - my $ref = \*{$name}; - delete $::{$name}; - return $ref; - } -} |