#!/usr/local/bin/perl
# $Id$
$VERSION{'PUBLIC'} = '2.001';
$VERSION{''.__FILE__} = '$Revision$';
#
# >>Title:: SDF Conversion Utility
#
# >>Copyright::
# Copyright (c) 1992-1996, Ian Clatworthy (ianc@mincom.com).
# You may distribute under the terms specified in the LICENSE file.
#
# >>History::
# -----------------------------------------------------------------------
# Date Who Change
# 29-Feb-96 ianc SDF 2.000
# -----------------------------------------------------------------------
#
# >>Purpose::
# {{CMD:sdf}} converts [[SDF]] files to other document formats.
#
# >>Description::
# !SDF_OPT_STD
#
# The -2 option is a convenient way of specifying the alias (collection
# of options) which generates the output you want. e.g.
#
# V: sdf -2html abc
#
# is equivalent to:
#
# V: sdf +sdf2html abc
#
# The -D option is used to define variables. These are typically
# used for controlling conditional text and substituting text which
# changes. The format used is:
#
# V: -Dvariable1=value1,variable2=value2
#
# A flag is a shorthand way of specifying variables in the DOC family.
# i.e. -ftoc=3 is equivalent to -DDOC_TOC=3.
# The format of the -f option is:
#
# V: -fflag1=value1,flag2=value2
#
# If a variable or flag is specified without a value, 1 is assumed.
#
# To generate [[HTML]] topics, the command is:
#
# V: sdf -2topics abc
#
# By default, this will create sub-topics for each heading already in a
# separate file. It will also {{autosplit}} level 1 headings into sub-topics.
# The -n option can be used to control which level headings are split at:
#
# * 1 autosplits on level 1 headings (the default)
# * 2 autosplits on level 2 headings
# * 3 autosplits on level 3 headings
# * 0 disables autosplitting.
#
# Include files are searched for in the current directory,
# then in the directories given by the -I option,
# then in the default library directory.
#
# By default, {{CMD:sdf}} is configured to {{prefilter}} files with
# certain extensions. For example:
#
# V: sdf mytable.tbl
#
# is equivalent to executing {{CMD:sdf}} on a file which only contains:
#
# V: !include "mytable.pl"; table
#
# The -p option can be used to explicitly prefilter files or to
# override the default prefilter used. If a parameter is not provided,
# the prefilter is assumed to be {{table}}.
#
# The -a option can be used to specify parameters for the prefilter.
# For example:
#
# V: sdf -aformat='15,75,10' mytable.tbl
#
# The -P option prefilters the input files as programming languages.
# The parameter is the language to use. If none is provided, the
# extension is assumed to be the language name. For example:
#
# V: sdf -P myapp.c
#
# is equivalent to executing {{CMD:sdf}} on a file which only contains:
#
# V: !include "myapp.c"; example; wide; lang='c'
#
# The -N option adds line numbers at the frequency given.
# The default frequency is 1. i.e. every line.
#
# The -g option prefilters the input files by executing {{CMD:sdfget}}
# using the default report (default.sdg). To change the report used,
# specify the report name as the parameter.
# If the report name doesn't include an extension, sdg is assumed.
#
# Note: {{CMD:sdfget}} searches for reports in the current directory,
# then in the {{stdlib}} directory within SDF's library directory.
#
# The -r option runs the nominated SDR report on each input before formatting.
# In other words, SDR reports provide a mechanism for:
#
# * analysing the SDF just before it would be formatted, and
# * replacing that SDF with the output of the report (also SDF)
# so that the final output is a nicely formatted report.
#
# For example, the {{sdf_dir}} report generates a directory (tree) of
# the components (files) included in an SDF document.
# Reports are stored in {{sdr}} files and are searched for using the
# usual rules.
#
# The -L option can be used to specify a {{locale}}.
# The default locale name is specified in F<sdf.ini>.
# Locale naming follows POSIX conventions (i.e. language_country), so
# the locale name for American english is {{en_us}}.
# The information for each locale is stored in the F<locale> directory,
# so you'll need to have to look in there to see what locales are
# available. (As the default locale can be set in F<sdf.ini>, this
# isn't as ugly as it first sounds.)
#
# Note: At the moment, a locale file simply contains a list of language
# specific strings. Ultimately, it should be extended to
# support localisation of date and time formats.
#
# The -k option is used to specify a {{look}}.
# The default look library is specified in {{FILE:sdf.ini}}.
#
# The -s option can be used to specify a document {{style}}.
# Typical values are:
#
# * {{document}} - a technical document
# * {{memo}} - a memo
# * {{fax}} - a facsimile
# * {{minutes}} - minutes of a meeting.
#
# The -S option is used to specify the page size.
# Values supported include:
#
# !block table
# Name Width Height Comment
# global 21.0cm 11.0in will fit on either A4 or letter
# A3 29.7cm 42.0cm
# A4 21.0cm 29.7cm
# A5 14.8cm 21.0cm
# B4 25.7cm 36.4cm
# B5 17.6cm 25.0cm
# letter 8.5in 11.0in
# legal 8.5in 14.0in
# tabloid 11.0in 17.0in
# !endblock
#
# Additional page sizes can be configured in {{FILE:sdf.ini}}.
# To specify a rotated version of a named page size, append an {{R}}.
# For example, A4R implies a width of 29.7cm and a height of 21cm.
# A custom page size can also be specified using the format:
#
# V: {{width}}x{{height}}
#
# where {{width}} and {{height}} are the respective sizes in points.
#
# The -c option is used to specify a configuration library.
#
# A list of modules to use can be specified via the -u option.
#
# The initial heading level to start on can be specified via
# the -H option. This is useful if you want to preview how a
# topic will be displayed without regenerating the complete document.
# If a topic begins with a level 1 heading (e.g. H1) and you wish to
# format it as a document (i.e. the level 1 text becomes the DOC_NAME
# for {{MAC:build_title}}), use the -H option with a value of 0.
#
# The look of headings can also be adjusted. By default, H-style
# headings are numbered, A-style headings are lettered and P-style
# headings are plain. To force a particular style for all headings,
# the -K option can be used. Sensible parameter values are H, A and P
# although other values may work depending on what paragraph styles
# are configured at your site.
#
# The -d option is used to specify the format driver.
# Values supported include:
#
# * {{expand}} - format as expanded text (the default)
# * {{mif}} - Maker Interchange Format
# * {{pod}} - Plain Old Documentation (as used by [[Perl]]).
#
# Additional drivers can be configured in {{FILE:sdf.ini}}.
#
# The -y option can to used to specify a post-filter.
#
# The -z option can be used to specify a list of post-processing
# actions you want to execute on each output file after it is
# generated. The actions supported include:
#
# * {{ps}} - generate PostScript
# * {{doc}} - generate a Frame (binary) file
# * {{fvo}} - generate a Frame View-Only file
# * {{txt}} - generate a text file
# * {{rtf}} - generate an RTF file
# * {{clean}} - delete the output file (must be last).
#
# Additional actions can be configured in {{FILE:sdf.ini}}.
# By convention, the generated files are given the same names
# as the action keywords.
#
# The -t option is used to specify the logical target format.
# If none is specified, the default is the first post processing
# action, if any. Otherwise, the default is the format driver name.
#
# The -v option enables verbose mode. This is useful for debugging
# problems related to post processing. In particular, post processing
# actions containing the pattern {{clean}} are skipped in verbose mode.
# You can also switch off the post processing messages by using a
# verbose value of -1. Values higher than 1 switch on additional
# trace messages as follows:
#
# . 2 - show how names of files and libraries are resolved
# . 3 - show the directories searched for libraries
# . 4 - show the directories searched for modules
# . 5 - show the directories searched for normal files.
#
# The -T option can be used to switch on
# debug tracing. The parameter is a comma-separated list of name-value
# pairs where each name is a {{tracing group}} and each value is the
# level of tracing for that group. To get the trace output provided
# by the -v option, one can use the {{user}} group like this:
#
# > sdf -Tuser=2 ...
#
# This is slightly different from the -v option in that intermediate
# files are not implicitly kept. Additional tracing groups will be
# added over time (probably one per output driver).
#
# The -w option is used to specify the width for text-based outputs.
#
# The -z, -D, -f and -I options are list options. i.e. multiple values
# can be separated by commas and/or the options can be supplied
# multiple times.
#
# >>Examples::
#
# Convert {{FILE:mydoc.sdf}} to a technical document in mif format,
# output is {{FILE:mydoc.mif}}:
#
# V: sdf -2mif mydoc.sdf
#
# Convert {{FILE:mydoc.sdf}} to online documentation in {{PRD:FrameViewer}}
# format, output is {{FILE:mydoc.fvo}}:
#
# V: sdf -2fvo mydoc.sdf
#
# Convert {{FILE:mydoc.sdf}} to online documentation in HTML,
# output is {{FILE:mydoc.html}}:
#
# V: sdf -2html mydoc.sdf
#
# The following command will build the reference documentation
# for a SDF module in HTML:
#
# V: sdf -2html abc.sdm
#
# >>Limitations::
# Many of the default post processing (-z) actions only works on [[Unix]] as
# [[FrameMaker]] for [[Windows]] does not support batch conversion.
#
# Topics mode has several limitations:
# * only documents in the current directory can be converted
# * all sub-topics must also be in the current directory.
#
# >>Resources::
#
# >>Implementation::
#
require "sdf/app.pl";
require "sdf/parse.pl";
########## Initialisation ##########
# define configuration
%app_config = (
'inifile', 'sdf.ini',
'libdir', 'sdf/home',
);
# Table of format mappings
%format_mappings = ();
# routine for handling the 2 option
sub MyToAlias {
local($arg) = @_;
my $alias = $format_mappings{$arg} || $arg;
&AppMsg("app", "formatting using '$alias'") if $verbose;
unshift(@ARGV, "+sdf2$alias");
}
# define options
push(@app_option, (
#'Name|Spec|Help',
'format;2|ROUTINE-MyToAlias;|the output format you want',
'variable;D|STRHASH|define variables',
'split_level;n|INT|heading level to autosplit into topics',
'flag|STRHASH|define flags (i.e. DOC_* variables)',
'include_path;I|STRLIST|search path for include files, templates, etc.',
'prefilter|STR;;table|pre-filter input file from each argument',
'parameters;a|STR|parameters for the pre-filter',
'plang;P|STR;;-|pre-filter as a programming language',
'line_numbers;N|INT;;1|number lines in pretty-printed source code',
'get_report|STR;;default|pre-filter using sdfget with the report specified',
'report|STR|report to run on the SDF to transform it before formatting',
'locale;L|STR|locale',
'look;k|STR|look library',
'style|STR|style of document',
'page_size;S|STR|page size for paper documents',
'config|STR|configuration library',
'uses|STRLIST|modules to use',
'head_level;H|INT|initial heading level',
'head_look;K|STR|heading look (H, A or P)',
'driver|STR;expand|format driver - default is expand',
'post_filter;y|STR|filter to post-filter the output with',
'post_process;z|STRLIST|list of post processing actions to do',
'target|STR|logical target format',
'verbose|INT;;1|verbose mode',
'trace_levels;T|STRHASH|debugging trace levels',
'width|INT|width for text-based outputs',
'library_path;Y|STRLIST|search path for libraries',
));
# configuration tables
%prefilter_for = ();
%post_processing = ();
%predefined_vars = ();
# ini-file handler
sub iniProcess {
local($fname, *data) = @_;
# local();
local($section, @pagesizes, @prefilters, @drivers, @conversions);
for $section (sort keys %data) {
if ($section eq 'Frame') {
%values = &AppSectionValues($data{$section});
$sdf_fmext = $values{'fm_ext'};
delete $data{$section};
}
elsif ($section eq 'PageSizes') {
@pagesizes = &TableParse(split(/\n/, $data{$section}));
&SdfLoadPageSizes(@pagesizes);
delete $data{$section};
}
elsif ($section eq 'PreFilters') {
@prefilters = &TableParse(split(/\n/, $data{$section}));
&LoadPreFilters(@prefilters);
delete $data{$section};
}
elsif ($section eq 'Drivers') {
@drivers = &TableParse(split(/\n/, $data{$section}));
&SdfLoadDrivers(@drivers);
delete $data{$section};
}
elsif ($section eq 'PostProcessing') {
%post_processing = &AppSectionValues($data{$section});
delete $data{$section};
}
elsif ($section eq 'Variables') {
%predefined_vars = &AppSectionValues($data{$section});
delete $data{$section};
}
elsif ($section eq 'FormatMappings') {
%format_mappings = &AppSectionValues($data{$section});
delete $data{$section};
}
elsif ($section eq 'Conversions') {
@conversions = &TableParse(split(/\n/, $data{$section}));
&NameLoadConversionRules(*conversions, $verbose);
delete $data{$section};
}
}
}
# Prefilter validation rules
@_PREFILTER_RULES = &TableParse(
'Field Category',
'Name key',
'Aliases optional',
);
# This routine loads the prefilters
sub LoadPreFilters {
local(@data) = @_;
# local();
local(@fld, $rec, %value);
local($name, $alias);
# Validate the table
&TableValidate(*data, *_PREFILTER_RULES);
# Process the prefilter table
@fld = &TableFields(shift(@data));
for $rec (@data) {
%value = &TableRecSplit(*fld, $rec);
$name = $value{'Name'};
$prefilter_for{$name} = $name;
for $alias (split(/,/, $value{'Aliases'})) {
$prefilter_for{$alias} = $name;
}
}
}
# handle options
&AppInit('sdf_file ...', 'convert an sdf file to another format', 'SDF',
'iniProcess') || &AppExit();
# Initialise the tracing levels
%app_trace_level = %trace_levels;
$app_trace_level{"user"} = $verbose if $verbose > $app_trace_level{"user"};
# Initialise the SDF module to use the correct search paths
@sdf_include_path = @include_path;
@sdf_library_path = @library_path;
$sdf_lib = $app_lib_dir;
# Check the page size is defined or in the form width"x"height
if ($page_size ne '') {
unless ($sdf_pagesize{$page_size} || $page_size =~ /^[\d\.]+x[\d\.]+$/) {
&AppExit("fatal", "page size '$page_size' not supported.");
}
}
# Check the format driver is defined
unless ($sdf_driver{$driver}) {
&AppExit("fatal", "format driver '$driver' not supported.");
}
# Check the post processing actions are configured and define the
# matching variables
for $action (@post_process) {
unless ($post_processing{$action}) {
&AppExit("fatal", "post-processing '$action' not supported.");
}
$name = $action;
$name =~ tr/a-z/A-Z/;
$variable{"OPT_PP_$name"} = 1;
}
# Assign a default logical target, if necessary
if ($target eq '') {
$target = @post_process ? $post_process[0] : $driver;
}
# Make the predefined variables
for $var (keys %predefined_vars) {
$variable{$var} = $predefined_vars{$var};
}
# Map flags to variables
while (($flag, $value) = each %flag) {
$flag =~ tr/a-z/A-Z/;
$flag = "DOC_$flag";
$variable{$flag} = $value unless defined $variable{$flag};
}
# When pretty-printing programming languages, 'listing' is the style used
$style = 'listing' if $plang ne '';
# Map the key configuration options to variables
$variable{'OPT_LOCALE'} = ($locale eq '.' ? '' : $locale) if $locale ne '';
$variable{'OPT_LOOK'} = ($look eq '.' ? '' : $look) if $look ne '';
$variable{'OPT_STYLE'} = ($style eq '.' ? '' : $style) if $style ne '';
$variable{'OPT_CONFIG'} = $config;
$variable{'OPT_DRIVER'} = $driver;
$variable{'OPT_TARGET'} = $target;
$variable{'OPT_EXT'} = $out_ext;
$variable{'OPT_NUMBER'} = $line_numbers;
$variable{'OPT_REPORT'} = $report;
$variable{'OPT_SPLIT_LEVEL'} = $split_level;
$variable{'OPT_HEAD_LEVEL'} = $head_level;
$variable{'OPT_HEAD_LOOK'} = $head_look;
$variable{'OPT_PAGE_SIZE'} = $page_size if $page_size ne '';
$variable{'OPT_WIDTH'} = $width if $width ne '';
# Map other interesting things to variables. Note that we let the
# user override these things on the command line if they want to.
$variable{'SDF_VERSION'} = $app_product_version unless $variable{'SDF_VERSION'};
$variable{'SDF_HOME'} = $app_lib_dir unless $variable{'SDF_HOME'};
# When regression testing, make sure that version changes don't cause problems
if ($variable{'SDF_TEST'}) {
$variable{'SDF_VERSION'} = 'x.y';
}
# When testing, '.' can be used to clear the look and/or style
$look = '' if $look eq '.';
$style = '' if $style eq '.';
########## Argument Processing ##########
sub argProcess {
local($ARGV) = @_;
local($main_arg_err);
local($msg_cursor, %msg_counts);
local($new, @new);
# If no output is nominated but post-processing is requested,
# just do the post processing on the input files
return 0 if @post_process && $out_ext eq '';
# Get the current message cursor
$msg_cursor = &AppMsgNextIndex();
# Generate result
@new = &GenerateDocument($ARGV);
# Check for problems
%msg_counts = &AppMsgCounts($msg_cursor);
if ($msg_counts{'error'} ||
$msg_counts{'abort'} ||
$msg_counts{'fatal'} ) {
return 1;
}
# Output result
for $new (@new) {
print "$new\n";
}
return 0;
}
# This routine generates a document
sub GenerateDocument {
local($ARGV) = @_;
local(@new);
local($file_ext);
local($filter, $module);
local($lang);
local($input_str);
local($ok_sdf, @sdf_data);
$file_ext = (&NameSplit($ARGV))[2];
# Handle programming language and sdfget pre-filtering
if ($plang ne '') {
$lang = $plang eq '-' ? $file_ext : $plang;
$prefilter = "example; wide; pure; lang='$lang'";
}
elsif ($get_report ne '') {
$prefilter = "get; report='$get_report'";
}
# Generate the SDF file if we're pre-filtering the input
$filter = $prefilter ne '' ? $prefilter : $prefilter_for{$file_ext};
if ($filter ne '') {
# When prefiltering pod, assume the main parameter
if ($filter eq 'pod') {
$filter .= "; main";
}
$filter .= "; $parameters" if $parameters ne '';
@sdf_data = (
"!_bof_ '$ARGV'",
"!include '$ARGV'; $filter",
"!_eof_");
}
# If we haven't built the SDF by now, fetch it from the file
else {
($ok_sdf, @sdf_data) = &SdfFetch($ARGV);
unless ($ok_sdf) {
&AppMsg('abort', "error fetching sdf file '$ARGV'");
return ();
}
}
# Generate and return the required format
@new = &SdfConvert(*sdf_data, $driver, *uses, %variable);
# Post filter the output, if requested
if ($post_filter ne '') {
&AppMsg("object", "'$post_filter' post filtering...") if $verbose >= 0;
require "sdf/post_$post_filter.pl";
$fn = $post_filter . "_PostFilter";
@new = eval {&$fn(*new)};
&AppMsg('failed', $@) if $@;
}
# Return result
return @new;
}
# This is called AFTER output streams have been closed for each argument
sub argPostProcess {
local($ARGV, $main_arg_err) = @_;
# local();
local($action);
# If an error occurred in the main processing routine or there
# is nothing to do, just return
return if $main_arg_err || scalar(@post_process) == 0;
# Ensure the necessary variables are exported to the 'user' package
# Note: If post-processing is the only thing requested, the output
# extension is taken from the input filename
$SDF_USER'short = (&NameSplit($ARGV))[1];
$SDF_USER'long = &NameJoin($out_dir, $SDF_USER'short, '');
$SDF_USER'out_ext = $out_ext eq '' ? (&NameSplit($ARGV))[2] : $out_ext;
$SDF_USER'width = $SDF_USER'var{'OPT_WIDTH'};
$SDF_USER'title = $SDF_USER'var{'DOC_TITLE'};
$SDF_USER'title =~ s/['\\]/\\$&/g;
$SDF_USER'project = $SDF_USER'var{'DOC_PROJECT'};
$SDF_USER'project =~ s/['\\]/\\$&/g;
$SDF_USER'product = $SDF_USER'var{'DOC_PRODUCT'};
$SDF_USER'product =~ s/['\\]/\\$&/g;
$SDF_USER'section = $SDF_USER'var{'DOC_SECTION'} || 1;
# Do the post processing
for $action (@post_process) {
if (!defined($post_processing{$action})) {
&AppExit("failed", "unknown special command '$action'");
}
else {
next if $verbose && $action =~ /clean/;
&AppMsg("object", "'$action' post processing...") if $verbose >= 0;
package SDF_USER;
eval $'post_processing{$'action};
if ($@) {
&'AppMsg('abort', "error in '$action' post processing: $@");
}
}
}
}
# switch back to the main package
package main;
########## Main Program ##########
&AppProcess('argProcess', 'argPostProcess', 'sdf');
&AppExit();