(*(***********************************************************************

:Program.    Autodoc.mod
:Contents.   extracts autodocs from source
:Author.     Hartmut Goebel [hG]
:Address.    Aufseßplatz 5, D-8500 Nürnberg 40
:Address.    UseNet: hartmut@oberon.nbg.sub.org
:Address.    Z-Netz: hartmut@asn.zer   Fido: 2:246/81.1
:Copyright.  Copyright © 1993 by Hartmut Goebel
:Language.   Oberon
:Translator. Amiga Oberon V3.0
:History.    V2.0, 03 Jan 1993 [hG]
:Version.    $VER: Autodoc.mod 2.7 (22.1.93)

(* $StackChk- $NilChk- $RangeChk- $CaseChk- $OvflChk- $ReturnChk- $ClearVars- *)
***********************************************************************)*)
(****i* autodoc/history ************
*  V2.7  - new option -d to create the --interface-- entry
*        - error double entry now outputs entry name
*        - can handle entry names including spaces - ' *' terminates name
*  V2.6  - implemented all original options execpt -c, -t<num>
*
********)
(****** autodoc ************
*
*   NAME
*       autodoc - Extracts and sorts autodocs from the given files to
*                 stdout.
*
*   SYNOPSIS
*       autodoc [ -i ] [ -a ] [ -s ] [ -m ] [ -C ] [-d]
*               [ -w ] [ -tnum ] [ -lnum ] [ -c ] [ -f ] [ -I ] {files}
*
*   DESCRIPTION
*       autodoc extracts autodocs from files {files}
*       Sorts the output to stdout.
*
*       Options offered by autodoc are the following:
*
*       -i      Extract ONLY internal autodocs (autodocs that have an 'i'
*               as the 6th character in a line that begins an autodoc).
*       -a      Process autodocs that begin with asterisk in the first
*               column.
*       -s      Process autodocs that begin with semicolon in the first
*               column.
*       -m      Process autodocs in Modula-2 language form.
*       -C      Process autodocs in C language form.
*
*       -tnum   Convert tabs into num spaces. (not yet implamented)
*       -lnum   Set the line length to num. (default = 78 chars, max. 256).
*       -w      Turn off word wrap. Chop lines longer than line length.
*
*       -d      include definition module as entry --interface--
*       -c      Convert C comments. From backslash asterisk to slash
*               asterisk. This allows comments in autodocs without nesting
*               comments. (not yet implemented)
*
*       -f      Disable form feeds between autodoc entries.
*       -I      Include a TABLE OF CONTENTS as the first part of the output.
*
*   USAGE
*       autodoc sends formatted file to standard output. User could redirect
*       this output to a file.
*
*               autodoc > outfile file
*
*       With the -d option there will automaticly be generated an entry
*       --interface-- if the filename ends with '.mod' and there exists a
*       corresponting '.def'-file. The contents of this file will be included
*       in this entry (without wordwrap).
*
*       Ex. to extract ALL possible autodocs except internal autodocs,
*       generate entry --interfaec-- and print with a table of contents.
*
*               autodoc > outfile -a -s -m -d -C -I file1
*
*       This filter must be used only with text files.
*
*   AUTHOR
*       hartmut Goebel
*
*********)

MODULE Autodoc;

IMPORT
  args:= Arguments,
  Break,  (* checks for Ctrl-C *)
  Conversions,
  asc := ASCII,
  avl := AVL,
  fs := FileSystem,
  io,
  ms := MoreStrings,
  str := Strings;

CONST
  versionString = "$VER: autodoc 2.7 (22.1.93)";
  titleString = "Autodoc 2.7 by hartmut Goebel";

TYPE
  Entry = POINTER TO EntryDesc;
  EntryDesc = RECORD (avl.SNode)
    start: LONGINT;
    interface: BOOLEAN;
  END;

VAR
  nArgs, argCnt: INTEGER;
  file: fs.File;
  root: avl.SRoot;
  filename, text: ARRAY 256 OF CHAR;

CONST
  defaultTextWidth = 78; maxTextWidth = SIZE(text);
  defaultTabSize = 8;

VAR
  internal: BOOLEAN;
  toc, ff, wordWrap: BOOLEAN;
  cStyle, asteric, modula, semicolon, definition: BOOLEAN;
  textWidth, tabSize: LONGINT;

PROCEDURE TableOfContents(node: avl.NodePtr);
BEGIN
  WITH node: Entry DO
    io.WriteString(node.name);
    io.WriteLn;
  END;
END TableOfContents;


PROCEDURE WriteEntry(node: avl.NodePtr);

  PROCEDURE WrapString();
  VAR
    c: CHAR;
  BEGIN
    IF wordWrap THEN
      LOOP
        IF str.Length(text) < textWidth THEN EXIT END;
        c := text[textWidth];
        text[textWidth] := CHR(0);
        io.WriteString(text); io.WriteLn;
        text[textWidth] := c;
        str.Delete(text,0,textWidth);
      END;
    END;
    io.WriteString(text); io.WriteLn;
  END WrapString;

VAR
  i: INTEGER;
  eFile: fs.File;
BEGIN
  WITH node: Entry DO
    io.WriteString(node.name);
    (* $RangeChk- *)
    i := SHORT(textWidth) - 2 * SHORT(str.Length(node.name)); (* name is max 80 char *)
    (* $RangeChk= *)
    WHILE i > 0 DO
     io.Write(" "); DEC(i);
    END;
    io.WriteString(node.name);
    io.WriteLn;
    IF node.interface THEN
      IF ~ fs.Open(eFile,filename,FALSE) THEN
        io.WriteString("can't open .def"); io.WriteLn;
        HALT(20);
      END;
      io.WriteLn;
      WHILE fs.ReadString(eFile,text) DO
        WrapString;
      END;
      IF ff THEN io.Write(asc.ff); END;
      IF fs.Close(eFile) THEN END;
    ELSE
      IF ~ fs.Move(file,node.start) THEN
        io.WriteString("error in file");
        io.WriteLn;
        HALT(20);
      END;
      WHILE fs.ReadString(file,text) DO
        str.Delete(text,0,1);
        IF ms.StrCmpN(text,"***",3) = 0 THEN
          IF ff THEN io.Write(asc.ff); END;
          RETURN;
        END;
        WrapString;
      END;
    END;
  END;
END WriteEntry;


PROCEDURE ProcessFile();
VAR
  entry: Entry;
  i: LONGINT;
  adtext: ARRAY 10 OF CHAR;

  PROCEDURE IsEntryStart(): BOOLEAN;
  CONST
    adTextLen  = 5; intPos = 5;
    astText  = "*****";
    modText  = "(****";
    semText  = ";****";
    cStText  = "/****";

  BEGIN
    RETURN (  internal & (text[intPos] = "i") OR
            ~ internal & (text[intPos] = "*"))
         & (text[intPos+1] = "*") & (text[intPos+2] = " ")
         & (asteric   & (ms.StrCmpN(astText,text,adTextLen) = 0) OR
            modula    & (ms.StrCmpN(modText,text,adTextLen) = 0) OR
            semicolon & (ms.StrCmpN(semText,text,adTextLen) = 0) OR
            cStyle    & (ms.StrCmpN(cStText,text,adTextLen) = 0));
  END IsEntryStart;

BEGIN
  IF ~ fs.Open(file,filename,FALSE) THEN
    io.WriteString("can't open file "); io.WriteString(filename); io.WriteLn;
    HALT(20);
  END;
  avl.SInit(root);

  (* check for .def *)
  IF definition THEN
    i := SHORT(str.Occurs(filename,".mod"));
    IF (i > 0) & (i = str.Length(filename)-4) THEN
      filename[i] := CHR(0);
      text := filename;
      str.Append(filename,".def");
      IF fs.Exists(filename) THEN
        NEW(entry);
        i := ms.OccursCharPos(text,"/",-SHORT(str.Length(text)-1));
        IF i >= 0 THEN
          str.Delete(text,0,i+1);
        END;
        str.Append(text,"/--interface--");
        COPY(text,entry.name);
        entry.interface := TRUE;
        IF avl.SAdd(root,entry) THEN END;
      END;
    END;
  END;

  WHILE fs.ReadString(file,text) DO
    IF IsEntryStart() THEN
      str.Delete(text,0,8);
      i := str.Occurs(text," *"); (* search for end of text *)
      IF i >= 0 THEN
        text[i] := CHR(0);
      END;
      NEW(entry);
      COPY(text,entry.name);
      entry.start := fs.Position(file);
      entry.interface := FALSE;
      IF ~ avl.SAdd(root,entry) THEN
        io.WriteString("double entry: ");
        io.WriteString(text);
        io.WriteLn;
        HALT(20);
      END;
    END;
  END;
  IF file.status # fs.eof THEN
    io.WriteString("error reading file");
    io.WriteLn;
    HALT(20);
  END;
  IF toc THEN
    io.WriteString("TABLE OF CONTENTS");
    io.WriteLn; io.WriteLn;
    avl.DoForward(root,TableOfContents);
    IF ff THEN io.Write(asc.ff); END;
  END;
  avl.DoForward(root,WriteEntry);
 IF fs.Close(file) THEN END;
END ProcessFile;


BEGIN
  filename := versionString;

  nArgs := args.NumArgs();
  IF (nArgs = 0) THEN
    io.WriteString(titleString); io.WriteLn;
    io.WriteString("Usage: autodoc [options] infile [infile]"); io.WriteLn;
    io.WriteLn;
    io.WriteString("Options"); io.WriteLn;
    io.WriteString("-i     - Process only INTERNAL autodocs"); io.WriteLn;
    io.WriteString("-a     - Process autodocs starting with an asteric"); io.WriteLn;
    io.WriteString("-s     - Process autodocs starting with a semicolon"); io.WriteLn;
    io.WriteString("-m     - Process autodocs starting with (*"); io.WriteLn;
    io.WriteString("-C     - Process autodocs starting with /*"); io.WriteLn;
    io.WriteString("-w     - Turn off word wrap"); io.WriteLn;
    io.WriteString("-d     - include definition module as entry --interface--"); io.WriteLn;
    io.WriteString("-f     - No form feeds between entries"); io.WriteLn;
    io.WriteString("-I     - Output Table of Contents before entries"); io.WriteLn;
    io.WriteString("-l<num>- Set line length to <num> (default 78, max. 256)"); io.WriteLn;
    HALT(20);
  END;

  internal := FALSE; cStyle := FALSE; asteric := FALSE; modula := FALSE;
  semicolon := FALSE;
  toc := FALSE; ff := TRUE; wordWrap := TRUE;
  textWidth := defaultTextWidth-1; tabSize := defaultTabSize;

  argCnt := 0;
  REPEAT
    INC(argCnt);
    args.GetArg(argCnt,filename);
    IF filename[0] # "-" THEN
      ProcessFile();
    ELSE
      CASE filename[1] OF
      "i": internal := TRUE; |
      "a": asteric := TRUE; |
      "s": semicolon := TRUE; |
      "m": modula := TRUE; |
      "d": definition := TRUE; |
      "C": cStyle := TRUE; |
      "w": wordWrap := FALSE; |
      "f": ff := FALSE; |
      "I": toc := TRUE; |
      "l": str.Delete(filename,0,2);
           IF Conversions.StringToInt(filename,textWidth) THEN
             DEC(textWidth);
             IF textWidth > maxTextWidth THEN
               textWidth := maxTextWidth;
             ELSIF textWidth < 0 THEN
               textWidth := 0;
             END;
           ELSE
             textWidth := defaultTextWidth;
           END;
      ELSE
      END;
    END;
  UNTIL argCnt = nArgs;
END Autodoc.
