#! @PERL@ # ==================================================================== # Copyright (c) 2001, MeepZor Consulting. # All rights reserved. # # The use and distribution of this code or document is # governed by version 1.0.1 of the MeepZor Consulting Public # Licence (MCPL), which may be found on the Internet at # . # # # $Id: mklicence.in,v 1.1 2001/05/15 18:03:46 coar Exp $ # Version: @PACKAGE_VERSION@ # # # When invoked from the shell, this script has two required options: # # mklicence -f format [-l file] [-h] [-v] [-d] [-s] [-u undefined-string] \ # [name:value ...] # # The rendered text is sent to STDOUT. # # -l file # Licence input file, containing text and definitions. STDIN # is used if omitted or is '-'. # # -f format # Format of the output (sent to STDOUT). Possible values are: # # html # Each line is emitted as an HTML comment, with sentinel lines # at the beginning and end of the block. # # text, txt # The edited text is simply emitted without any special formatting. # # c, cblock, c-block # The entire text is emitted as a single C block comment. # # cline, c-line # Each line is a standalone C comment, with sentinel lines at the # very beginning and very end of the output. # # c++, cxx # Each line is a standalone C++ '//' comment. # # man # Each line is a '.\"' comment. # # perl, pl # Each line is a Perl line comment beginning with "#". # # Other output languages can be added by simply updating the if-elsif # chain and adding a new handler routine. # # -d # Enables rudimentary debugging, and the .dump directive. # # -h # Displays the version and usage information. # # -s # Names defined by the command line cannot be overridden by definitions # in the file. # # -u undefined-string # Text string to substitute for undefined references. Default is "". # # -v # Displays the version and usage information. # # # Format of the input file: # # Special processing is signalled by the input line beginning with a # dot ("."). This indicates that the line is a definition or a directive. # # Lines of the form ".name:value" define the specified name with the # indicated value. Predefined names are "Format" and "LicenceFile", # and come from the mklicence command line. They may be redefined # by the input file. # # Defined names are substituted in subsequent lines (including # directives) using the syntax ${name}. The most recent definition # of a name replaces any existing one, regardless of its origin. # Substitution does *not* occur within definition names, only values. # E.g., ".a${foo}b:value" does not have the value of 'foo' inserted. # If a reference is made to an undefined name, the substitution # text will be that specified by the -u command line option. # # .if/.else/.endif structures are supported, but not .elseif. # The expression on a .if line is evaluated as a Perl expression; # if the result of the evaluation is true, the enclosed lines are # processed; otherwise they're ignored. For instance, # # .if "${Format}" =~ /text/i # # would evaluate as true IFF the output format was specified as # plaintext on the command line. # # .if/.else/endif structures may be nested. # # Lines beginning with ".#" are considered comments and are discarded. # # The ".dump" directive, if enabled with the -d option and found in a # section of active input (i.e., that's not being ignored due to conditional # processing), dispays the current name-value pair table on STDERR. # # A '.literal' directive turns off *all* interpretation until a # corresponding '.endliteral' directive is encountered. # use Getopt::Long; use File::Basename; # # Preload some of our cells. # %keywords = (); # Keywords defined in the input %ckeywords = (); # Keywords defined on the command line $editfunc = 0; # Reference to transformation function %arg = (); # Command-line arguments $inputfile = "-"; $depth = -1; $margin = 65; $output_line = 1; $fill = 0; $0 = basename($0); %formats = ('HTML' => \&make_html, 'man' => \&make_man, 'C-block|cblock|c' => \&make_c_block, 'C-line|cline' => \&make_c_line, 'Perl|pl' => \&make_perl, 'text|txt|plain|plaintext' => \&make_text, 'C\+\+|cxx' => \&make_cxx); # # Let's find out about our command arguments. # $got_options = GetOptions("format|f=s" => \$format_exact, "debug|d!" => \$DEBUG, "fill!" => \$fill, "help|h" => \$help, "margin=i" => \$margin, "supersede|s!" => \$no_override, "l=s" => \$inputfile, "undefined-default|u=s" => \$undefined_message, "version|v" => \$show_version); if ($DEBUG) { $ckeywords{'DEBUG'} = 1; &set_keyword('DEBUG', $ckeywords{'DEBUG'}); } if ($show_version || $help ) { if ($show_version) { &show_version(); } elsif ($help) { &usage(); } exit(0); } if (! $format_exact) { printf STDERR ("$0: required option -f missing\n"); exit 1; } elsif (($inputfile ne "-") && (! -f $inputfile)) { printf STDERR ("$0: can't access input file '$inputfile'\n"); exit 1; } printf STDERR ("mklicence version @PACKAGE_VERSION@\n"); # # Select the appropriate transformer. Here's where additional formats # may be added. Don't forget to include the corresponding handler # function at the end of this file! # if (! &set_format($format_exact, 1)) { printf STDERR ("$0: invalid value for -f\n"); exit 1; } push_format(0, $format_exact); # # Obtain the default substitution text if specified. # $undefined = $undefined_message || ""; # # Preload a couple of values for use by the input file. Pretend they # were added on the command line so the lock flag can apply to them. # &set_keyword('DEPTH', $depth); $ckeywords{'DEPTH'} = $depth; &set_keyword('MARGIN', $margin); $ckeywords{'MARGIN'} = $; &set_keyword('FILL', $fill); $ckeywords{'FILL'} = $; &set_keyword('FORMAT', $format_general); $ckeywords{'FORMAT'} = $format_general; &set_keyword('FORMAT_EXACT', $format_exact); $ckeywords{'FORMAT_EXACT'} = $format_exact; $VERSION = join(".", q{$Id: mklicence.in,v 1.1 2001/05/15 18:03:46 coar Exp $} =~ m:\s(\d+\.\d+)\s:); &set_keyword('VERSION', $VERSION); $ckeywords{'VERSION'} = $keywords{'VERSION'}; &set_keyword('INPUTFILE', $inputfile); $ckeywords{'INPUTFILE'} = $inputfile; # # Define any names from the command line. # foreach (@ARGV) { my($name, @value) = split(/:/, $_); &set_keyword($name, &substitute_keywords(join(':', @value), $undefined, %keywords)); $ckeywords{$name} = $keywords{$name}; } # # Now is the time for us to actually process the input. # @ifstart = (); # .if line numbers @ifstate = (); # State of echoing at each level $iflevel = 0; # Depth of .if nesting $echo = 1; # Whether lines should be processed $outerecho = 1; # Setting of $echo at the last .if level $line = 1; # Current line of the file (for errors) @output = (); # Output accumulator # # Step through all the input we got from the template file, processing # directives as we come to them. # $literal = 0; $echo = 1; @output = &process_file($inputfile); # # We've gone through all the lines; see if we've returned to state 0 # in our conditional processing. If not, complain about each of the # .ifs for which we didn't see a corresponding .endif. # if ($#ifstart >= 0) { foreach (@ifstart) { print STDERR ("$0: .if at $_ has no corresponding .endif\n"); } } # # Figure out what the length is of the longest (edited) line. # Note that we *don't* include the newline. # $longest = 0; foreach (@output) { my($length); my($line) = $_; chomp($line); $length = length($line); if ((length($line) > $longest) && ($line !~ m/^\.[^: ]/)) { $longest = $length; } } # # Initialise the transformation routine, and emit any text it generates. # $text = $editfunc->("BEGIN", $longest); print $text if ($text); # # Dump the output as transformed and saved. # ($next_xform, $next_format) = &pop_format(); $line = 0; foreach (@output) { $line++; if ($line >= $next_xform) { &set_format($next_format, 1); ($next_xform, $next_format) = &pop_format(); } print $editfunc->($_, $longest); } # # Now do any trailer/cleanup handling. # $text = $editfunc->("END", $longest); print $text if ($text); # # Cool! We're done! exit 0; sub push_format { my($line, $format) = @_; push(@format_stack, "$line,$format"); return 1; } sub pop_format { return split(m:,:, shift(@format_stack)); } # # Substitute any already-defined keywords.. # sub substitute_keywords { my($input, $undefined, %keywords) = @_; my($kw, $value); local($output); $output = $input; for $kw (sort(keys(%keywords))) { $value = $keywords{$kw}; $output =~ s:\${$kw}:$value:g; } # # Replace any leftover undefined references with a default string. # $output =~ s/\${[^: ]*}/$undefined/g; # # Heh. Turn any embedded special characters into the real things. # $output =~ s:":\\":gs; eval("\$output = \"$output\""); return $output; } # # Define a keyword in the global hash. # # We assume the globals $no_override, %keywords, and %ckeywords. # sub set_keyword { my($key, $value) = @_; my($ignored); $ignored = ($no_override && defined($ckeywords{$key})); if ($ignored) { if ($DEBUG) { print STDERR ("$0: ignored attempt to redefine locked value " . "for '$key'\n"); } return 0; } if ($DEBUG) { print STDERR ("$0: keyword '$key' "); print STDERR (($keywords{$key}) ? "redefined" : "defined"); print STDERR (" to '$value'\n"); } $keywords{$key} = $value; return 1; } # # Format to select the correct format.. # sub set_format { my($fmt, $set) = @_; my($k, $f, $gfmt, $efmt, @kw, %result) = (); # # Reset the keywords hash # @kw = keys(%formats); while (($k, $f) = each(%formats)) { if ($fmt =~ m/^(?:$k)$/i) { ($gfmt) = split(m:\|:, $k); $gfmt = "\$gfmt = qq($gfmt)"; eval($gfmt); $result{'editfunc'} = $f; $result{'format_general'} = $gfmt; $result{'format_exact'} = $fmt; last; } } if (! $gfmt) { return (); } if ($set) { $editfunc = $result{'editfunc'}; $format_general = $result{'format_general'}; $format_exact = $result{'format_exact'}; } &set_keyword('FORMAT', $result{'format_general'}); return %result; } # # Function to read & process an input file. The filename is the only # argument. Returns an array of output lines. # sub process_file { my($fname) = @_; my($short_fname) = ($fname eq "-" ? "STDIN" : basename($fname)); my(@raw, $input, @output); my($line) = 1; my(@remaining); # # Right; now read in the raw file. # if ($input ne "-") { open(RAW, "<$fname"); @raw = ; close(RAW); } else { @raw = ; } @remaining = @raw; $depth++; &set_keyword('DEPTH', $depth); foreach $input (@raw) { # # Take this line off the list, so we can insert files at the front # at need.. # &set_keyword('INPUT_LINE', $line); shift(@remaining); if ($input =~ m:^\.literal:) { $literal = 1; $line++; next; } elsif ($literal && ($input =~ m:^\.endliteral:)) { $literal = 0; $line++; next; } elsif ((! $literal) && $echo && ($input =~ m:\.margin\s+(\S+):i)) { my($newval) = &substitute_keywords($1, $undefined, %keywords); if ($newval =~ m:^\d+$:) { $margin = $newval; &set_keyword('MARGIN', $margin); } else { printf STDERR ("$0: malformed .margin directive " . "at $short_fname:$line\n"); } $line++; next; } elsif ((! $literal) && $echo && ($input =~ m:\.format\s+(\S+):i)) { my($newval) = &substitute_keywords($1, $undefined, %keywords); my(%result) = &set_format($newval, 0); if (! %result) { printf STDERR ("$0: unrecognised format '$newval' at " . "$short_fname:$line; format unchanged\n"); } else { printf STDERR ("$0: format changed to $newval " . "at $short_fname:$line\n") if ($DEBUG); &push_format($output_line, $newval); } $line++; next; } elsif ((! $literal) && $echo && ($input =~ m:\.fill$:i)) { if (! $fill) { $fill = 1; &set_keyword('FILL', $fill); } $line++; next; } elsif ((! $literal) && ($input =~ m:\.endfill$:i)) { if ($fill) { $fill = 0; &set_keyword('FILL', $fill); } else { printf STDERR ("$0: .fill not active; .endfill ignored at " . "$short_fname:$line\n"); } $line++; next; } elsif ((! $literal) && $echo && ($input =~ m:^\.include\s+(.*):i)) { my($includeit) = 1; $dloc = dirname($inputfile); if ($1 eq "-") { $newfile = $1; } else { $newfile = $dloc . ($dloc ? "/" : "") . $1; if (! -f $newfile) { printf STDERR ("$0: can't access include file " . "'$newfile' at $short_fname:$line\n"); $includeit = 0; } } if ($includeit) { printf STDERR ("$0: including contents of '$newfile' " . "at $short_fname:$line\n") if ($DEBUG); push(@output, &process_file($newfile)); } } elsif ((! $literal) && ($input =~ m:^\.if\s+(.*):i)) { my($expr) = $1; # # Entering a conditional block. Make a note of the 'indentation' # level, and note the current echo status as well as the new # one so .else processing will work. # $expr = &substitute_keywords($expr, $undefined, %keywords); $iflevel++; push(@ifstate, $echo); push(@ifstart, "$short_fname:$line"); $outerecho = $echo; $echo = $echo ? eval($expr) : 0; if ($DEBUG && $outerecho) { print STDERR ("$0: .if-expression '$1' ('$expr') at " . "$short_fname:$line " . "$line evaluated to '$echo'\n"); } } elsif ((! $literal) && ($input =~ m:^\.endif$:i)) { # # Close out a conditional block. Decrement the if-level, and # display an error if we're trying to close something that # isn't open. # $iflevel--; if ($iflevel < 0) { printf STDERR ("$0: superfluous .endif at " . "$short_fname:$line\n"); } else { $echo = pop(@ifstate); pop(@ifstart); } } elsif ((! $literal) && ($input =~ m:^\.else$:i)) { # # Handle an alternate leg in a conditional block. # if ($iflevel < 1) { print STDERR ("$0: superfluous .else at $short_fname:$line\n"); } else { $echo = $outerecho ? (! $echo) : 0; } } elsif ((! $literal) && ($input =~ m:^\.#:)) { # # This input line is a comment; ignore # it and do nothing. # } elsif ((! $literal) && $DEBUG && $echo && ($input =~ m:^\.dump$:i)) { foreach (sort(keys(%keywords))) { print STDERR ("$0: '$_' = '$keywords{$_}'\n"); } } elsif ($echo) { # # Nothing special about this line, control flow-wise, and we're # in echo mode, so check for other directives or store the edited # text for output if it isn't a directive. # my($line) = $input; if ((! $literal) && ($input =~ m,^\.([^:\s]+):\s*(.*)$,)) { # # Only [re]define this name if it isn't one of the locked # ones. # &set_keyword($1, &substitute_keywords($2, $undefined, %keywords)); } else { if (! $literal) { $line = &substitute_keywords($line, $undefined, %keywords); } push(@output, $line); $output_line++; &set_keyword('OUTPUT_LINE', $output_line); } } $line++; } $depth--; &set_keyword('DEPTH', $depth); return @output; } # # Transformation handlers. Each is called with an argument of "BEGIN" # before the first line of text, in case it needs to do any setup or # emit a sentinel such as "/*", and with "END" after the last line # for a corresponding reason. In between, it's called with two # arguments: the line of text to transform (including the newline), # and the length of the longest line that will need to be transformed # (for any right-justification processing). # # The handler returns the text to be emitted, or undef. # # # Convert an input line into something Perl can use as a comment. # sub make_perl { my($input, $longest) = @_; if (($input eq "BEGIN") || ($input eq "END")) { # # No special header or trailer processing. # return undef; } return "# $input"; } # # Convert an input line into plain text. # sub make_text { my($input, $longest) = @_; if (($input eq "BEGIN") || ($input eq "END")) { # # No special header or trailer processing. # return undef; } return $input; } # # Convert an input line into comments for a man file. # sub make_man { my($input, $longest) = @_; if (($input eq "BEGIN") || ($input eq "END")) { # # No special header or trailer processing. # return undef; } return '.\" ' . $input; } # # Convert an input line into a C++ comment. # sub make_cxx { my($input, $longest) = @_; if (($input eq "BEGIN") || ($input eq "END")) { # # No special header or trailer processing. # return undef; } return "// $input"; } # # Convert an input line into a block comment for C. # sub make_c_block { my($input, $longest) = @_; # # C block comments have special delimiters. # if ($input eq "BEGIN") { return "/**\n"; } elsif ($input eq "END") { return " **/\n"; } return " ** $input"; } # # Convert an input line into a line comment for C. # sub make_c_line { my($input, $longest) = @_; my($pad, $padw); my($edited) = $input; my($length); chomp($edited); $length = length($edited); $padw = $longest - $length; if (($input eq "BEGIN") || ($input eq "END")) { # # Generate a sentinel line.. # return "/*" . ("*" x ($longest + 2)) . "*/\n"; } $pad = " " x $padw; return "/* $edited$pad */\n"; } # # Convert an input line into an HTML comment. # sub make_html { my($input, $longest) = @_; my($edited, $pad) = ($input, ""); my($padw); chomp($edited); $padw = $longest - length($edited); $pad = " " x $padw; if (($input eq "BEGIN") || ($input eq "END")) { # # Generate a sentinel line. # return "\n";; } return "\n"; } sub show_version { printf STDERR ("mklicence, @PACKAGE_VERSION@\n\n"); } sub usage { &show_version(); printf STDERR <