#!/usr/pkg/bin/perl
# $Id$
$VERSION{'PUBLIC'} = '2.000';
$VERSION{''.__FILE__} = '$Revision$';
#
# >>Title::     Documentation Extraction 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:sdfget}} extracts documentation embedded in source code.
#
# >>Description::
# !SDF_OPT_STD
#
# The -f option can be used to specify a filename to use when
# formatting the output. This is useful when the text is coming from
# the standard input stream.
#
# The {{get-rule}} nominates the formatting of the embedded
# documentation to be extracted. All currently defined get-rules
# assume the documentation is in comment blocks in one of the
# following formats:
#
# V: >>section_title1::
# V: text of section 1, line 1
# V: text of section 1, line ..
# V: 
# V: >>section_title2::
# V: text of section 2, line 1
# V: text of section 2, line ..
# V: >>END::
# V: 
# V: >>section_title3:: text of section 3
#
# The first form is most commonly used. In this format, the text
# in a section extends until the end of the current "comment block"
# or the start of the next section, whichever comes first. The
# second form (i.e. explicitly specifying where the section ends) is
# useful if you wish to add some normal comments (i.e. non-documentation)
# which you do not want extracted. If the text is short, the
# third form can be used. Regardless of the format, if a section
# is found which is already defined, the text of the section is
# concatenated onto the existing text. This permits the documentation
# for each entity to be specified immediately above where it is
# defined in the source code.
#
# The -g option specifies the {{get-rule}} to use.
# The available get-rules differ on the prefix expected at the front
# of each line as shown below.
#
# !block table
# Rule          Prefix
# perl          #
# cpp           //
# c             * or /*
# fortran       c (with 5 preceding spaces)
# eiffel        --
# bat           rem
# !endblock
#
# Within C code, a trailing space is required after the characters above.
# For other languages, a trailing space is optional.
# Within FORTRAN code, the "c" character must be preceded by exactly
# 5 spaces. For other languages, zero or more whitespace characters are
# permitted before the characters above.
#
# For example, embedded documentation within C code looks like:
#
# V: /* >>Purpose::
# V:  * This library provides a high level interface
# V:  * to commonly used network services.
# V:  */
#
# If the -g option is not specified,
# {{perl}} is the default get-rule. If the -g option is specified
# without a parameter, the extension in lowercase of the filename
# (or the {{formatting filename}} if the text is coming from standard input)
# is used to guess the get_rule as shown below.
#
# !block table
# Rule          Extensions
# cpp           cpp, c++, cc, hpp, hpp, h, java, idl
# c             c
# fortran       fortran, for, f77, f
# eiffel        eiffel, ada
# bat           bat, cmd
# !endblock
#
# A report filename can be specified using the -r option.
# If the name doesn't include an extension, sdg is assumed.
# Reports provide a mechanism for:
#
# * selectively extracting sections, and
# * rudimentary reformatting (e.g. to [[SDF]])
#
# If no report is specified, all sections are output in the
# following format:
#
# V: section_title1
# V: section_text1
# V: 
# V: section_title2
# V: section_text2
#
# If -r is specified on its own, {{FILE:default.sdg}} is assumed.
# This report selects the set of sections (within the [[SDF]]
# documentation standards) which form the user documentation and
# formats them into [[SDF]].
# Details on the report format are specified below.
# Reports are searched for in the current directory,
# then in the {{stdlib}} directory within SDF's library directory.
#
# The -s option can be used to specify the scope of the documentation
# to be extracted. (This is an experimental feature and may change
# so most users should avoid using it.)
#
# The -i option outputs only those lines which the get-rule did
# not match. This option is useful for extracting non-documentation
# from a file to give just the code.
#
# Note: The -r option is ignored if -i is specified.
#
# The -v option enables verbose mode. This is useful for seeing
# which rule is being used for each file.
#
# >>Examples::
#
# To extract the user documentation from a [[SDF]] application
# written in C++ ({{CMD:xyz}}, say) and save it into {{FILE:xyz.sdf}}:
#
# V:      sdfget -gcpp -r -osdf xyz.cpp
#
# >>Resources::
#
# >>Limitations::
# It would be nicer if the get-rule was always guessed from the
# filename extension but changing the default from perl could
# break existing scripts. Therefore, get-rule guessing must be
# explicitly enabled by specifging the -g option without a
# parameter.
#
# >>Implementation::
#

require "sdf/app.pl";
require "sdf/dict.pl";
require "sdf/sdfget.pl";

########## Initialisation ##########

#
# >>_Description::
# {{Y:_SDFGET_RULES}} is the lookup table used to guess the get_rule.
# The key is the (lowercase) filename extension and the
# value is the get_rule to use. If a get_rule isn't specified and
# the filename extension isn't in this table, the get_rule is assumed
# to be 'perl'.
#
%_SDFGET_RULES = (
    'cpp',      'cpp',
    'c++',      'cpp',
    'cc',       'cpp',
    'hpp',      'cpp',
    'h',        'cpp',
    'java',     'cpp',
    'idl',      'cpp',
    'c',        'c',
    'fortran',  'fortran',
    'for',      'fortran',
    'f77',      'fortran',
    'f',        'fortran',
    'eiffel',   'eiffel',
    'ada',      'eiffel',
    'bat',      'bat',
    'cmd',      'bat',
);

#
# >>_Description::
# {{@_GET_RULES}} contains a table of {{get-rule}} definitions.
# Each rule is defined by the fields in the table below.
#
# !block table
# Field      Description
# Name       rule name
# Begin      regular expression matching the start of a key
# Sep        regular expression matching the end of the key
# End        regular expression matching the end of a description
# Prefix     regular expression matching the start of each line
# Desc       description
# !endblock
#
@_GET_RULES = &TableParse (
    'Name    Begin  Sep     End     Prefix          Desc',
    'perl    >>     ::\s*   >>END:: \s*#[ ]?        Perl/Shell source code',
    'cpp     >>     ::\s*   >>END:: \s*\/\/[ ]?     C++ source code',
    'c       >>     ::\s*   >>END:: \s*\/?\*[ ]     C source code',
    'fortran >>     ::\s*   >>END:: [ ]{5}c[ ]?     FORTRAN source code',
    'eiffel  >>     ::\s*   >>END:: \s*\-\-[ ]?     Eiffel/Ada source code',
    'table   >>     ::\s*   >>END:: \s*\/\*[ ]?     Mincom TP table files',
    'bat     >>     ::\s*   >>END:: \s*rem[ ]?      batch source code',
);

# Index into the rules table - we don't mind duplicates
@_get_rules_dupl = ();
%_GET_RULES_INDEX = &TableIndex(*_GET_RULES, *_get_rules_dupl, 'Name');
@_RULE_IDS = keys %_GET_RULES_INDEX;
push(@_RULE_IDS, '-');

# valid scope values
@_SDFGET_SCOPE = ('PUBLIC','PROTECTED','PRIVATE');

# define configuration
%app_config = (
    'libdir',   'sdf/home',
);

# define options
push(@app_option, (
    #'Name|Spec|Help',
    'formatting_filename|STR|filename to use when formatting the output',
    'get_rule|STR-@_RULE_IDS;perl;-|rule to use to get documentation',
    'rpt_file|STR;;default.sdg|report file',
    'scope|STR-@_SDFGET_SCOPE;PUBLIC|scope of documentation to be extracted',
    'inverse|BOOL|only output lines not extracted',
    'verbose|INT;;1|verbose mode',
));

# handle options
&AppInit('file ...', 'extract documentation embedded in source code', 'SDF') ||
  &AppExit();

# Initialise trace levels
$app_trace_level{"user"} = $verbose if $verbose > $app_trace_level{"user"};

# If supplied, fetch the report file.
@report = ();
if ($rpt_file) {
    local($rpt, $rpt_dir);

    my $rpt_ext = (&NameSplit($rpt_file))[2];
    $rpt_file = &NameJoin('', $rpt_file, 'sdg') if $rpt_ext eq '';
    $rpt_dir = "$app_lib_dir/stdlib";
    unless ($rpt = &NameFind($rpt_file, ".", $rpt_dir)) {
            &AppExit('fatal', "cannot find report-file '$rpt_file'");
    }
    ($all_ok, @report) = &TableFetch($rpt);
    $all_ok || &AppExit('fatal', "error opening report-file '$rpt': $!");
}

########## Processing ##########

sub argProcess {
    local($ARGV) = @_;
#   local();
    local($success, $buflistref);
    local($level) = 0;

    # Get the file extension
    my $filename = $ARGV eq '-' ? $formatting_filename : $ARGV;
    my $ext = (&'NameSplit($filename))[2];

    # Use the current file extension to guess the rule, if necessary
    my $rule = $get_rule;
    if ($rule eq '-') {
        $rule = $_SDFGET_RULES{"\L$ext"} || 'perl';
        &AppTrace("user", 1, "derived get_rule for filename '$ARGV' is '$rule'");
    }

    # Get the rule parameters
    my %rule = &TableLookup(*_GET_RULES, *_GET_RULES_INDEX, $rule);

    # Fetch the data
    ($success, $buflistref) = &DictFetch($ARGV,
        $rule{"Begin"},
        $rule{"Sep"},
        $rule{"End"},
        $rule{"Prefix"});
    return 1 unless $success;

    # Print report
    if ($inverse) {
        print grep(! /^$rule{"Prefix"}$/, @dict_rest);
    }
    else {
        # Patch $ARGV to the formatting filename if requested
        $ARGV = $formatting_filename if $formatting_filename ne '';
        &DictPrint(STDOUT, 0, 'main', $buflistref, @report);
    }
}

&AppProcess('argProcess');
&AppExit();
