summaryrefslogtreecommitdiffstats
path: root/scripts
diff options
context:
space:
mode:
authorPeter Maydell2020-09-30 00:19:39 +0200
committerPeter Maydell2020-09-30 00:19:39 +0200
commite344ffe73bd77e7067099155cfd8bf42b07ed631 (patch)
treecf85f68f00ec2c004fccb574ce35d7aec7afdbdf /scripts
parentMerge remote-tracking branch 'remotes/mst/tags/for_upstream' into staging (diff)
parentRemove texinfo dependency from docker and CI configs (diff)
downloadqemu-e344ffe73bd77e7067099155cfd8bf42b07ed631.tar.gz
qemu-e344ffe73bd77e7067099155cfd8bf42b07ed631.tar.xz
qemu-e344ffe73bd77e7067099155cfd8bf42b07ed631.zip
Merge remote-tracking branch 'remotes/armbru/tags/pull-qapi-2020-09-29' into staging
QAPI patches patches for 2020-09-29 # gpg: Signature made Tue 29 Sep 2020 20:54:51 BST # gpg: using RSA key 354BC8B3D7EB2A6B68674E5F3870B400EB918653 # gpg: issuer "armbru@redhat.com" # gpg: Good signature from "Markus Armbruster <armbru@redhat.com>" [full] # gpg: aka "Markus Armbruster <armbru@pond.sub.org>" [full] # Primary key fingerprint: 354B C8B3 D7EB 2A6B 6867 4E5F 3870 B400 EB91 8653 * remotes/armbru/tags/pull-qapi-2020-09-29: (29 commits) Remove texinfo dependency from docker and CI configs configure: Drop texinfo requirement Remove Texinfo related line from git.orderfile scripts/texi2pod: Delete unused script docs/devel/qapi-code-gen.txt: Update to new rST backend conventions scripts/qapi: Remove texinfo generation support tests/qapi-schema: Add test of the rST QAPI doc-comment output meson.build: Make manuals depend on source to Sphinx extensions meson.build: Move SPHINX_ARGS to top level meson.build file tests/qapi-schema: Convert doc-good.json to rST-style strong/emphasis qga/qapi-schema.json: Add some headings qapi: Use rST markup for literal blocks docs/interop: Convert qemu-qmp-ref to rST docs/interop: Convert qemu-ga-ref to rST docs/sphinx: Add new qapi-doc Sphinx extension qapi/machine.json: Escape a literal '*' in doc comment scripts/qapi/parser.py: improve doc comment indent handling scripts/qapi: Move doc-comment whitespace stripping to doc.py tests/qapi/doc-good.json: Prepare for qapi-doc Sphinx extension qapi/block.json: Add newline after "Example:" for block-latency-histogram-set ... Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
Diffstat (limited to 'scripts')
-rwxr-xr-xscripts/checkpatch.pl2
-rw-r--r--scripts/coverity-scan/coverity-scan.docker1
-rw-r--r--scripts/git.orderfile1
-rw-r--r--scripts/qapi-gen.py2
-rw-r--r--scripts/qapi/doc.py301
-rw-r--r--scripts/qapi/gen.py7
-rw-r--r--scripts/qapi/parser.py105
-rwxr-xr-xscripts/texi2pod.pl536
8 files changed, 79 insertions, 876 deletions
diff --git a/scripts/checkpatch.pl b/scripts/checkpatch.pl
index 50910899f2..1ba8a3810b 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";
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/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/&/&amp;/g;
- s/\@\{/&lbrace;/g;
- s/\@\}/&rbrace;/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/</&lt;/g;
- s/>/&gt;/g;
- } else {
- s/</&LT;/g;
- s/>/&GT;/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\&LT;$1\&GT;\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)\{([^\},]*)\}/&lt;B<$1>&gt;/g;
- s/\@uref\{([^\},]*),([^\},]*)\}/$2 (C<$1>)/g;
- s/\@uref\{([^\},]*),([^\},]*),([^\},]*)\}/$3/g;
-
- # Un-escape <> at this point.
- s/&LT;/</g;
- s/&GT;/>/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/&lt;/E<lt>/g;
- s/&gt;/E<gt>/g;
- s/&lbrace;/\{/g;
- s/&rbrace;/\}/g;
- s/&at;/\@/g;
- s/&amp;/&/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;
- }
-}