home *** CD-ROM | disk | FTP | other *** search
/ Power Programming / powerprogramming1994.iso / progtool / filutl / toadshr1.arc / TOADSHR1.PAS < prev   
Pascal/Delphi Source File  |  1989-06-14  |  36KB  |  1,012 lines

  1. PROGRAM ToadShar;
  2.  
  3. { Toad Hall Shar v1.1 for Turbo Pascal v5.0
  4.  
  5.   Other shars available for MS-DOS had no wildcard capability,
  6.   and I got BLOODY tired of typing in all those filenames!
  7.  
  8.   This one's got wildcard capability (both for shar creation and extraction).
  9.  
  10.   I've arbitrarily decided to force the world to accept the MS-DOS shar
  11.   file type of ".SHR" for a shar file type (when wildcard extracting)
  12.   (since we can't use the Unix ".shar" standard).
  13.  
  14.   You don't like it?  Recode it!
  15.  
  16.   No, I don't know how to change it for Turbo Pascal v4.0.
  17.  
  18.   Usage:
  19.     shar [-u] [file1]..[filen]
  20.  
  21.     Where file1..filen can be up to 20 MS-DOS path\filenames
  22.     (wildcarded if you wish).  (The 20 was arbitrary .. see MAXARGS.)
  23.  
  24.     -u     Unshar (extract) members from shar file(s).
  25.            Yes, the -u (any case) MUST be the first command line parameter!
  26.            shar filenames can be full DOS paths, with a default file
  27.            type of ".SHR" (added if required).
  28.  
  29.   During shar creation:
  30.     The .SHR files produced will be simplistic (e.g., none of the fancy
  31.     Unix switches are available).  However, they should be compatible
  32.     with Unix shars (provided you work around the line ending problem,
  33.     of course).
  34.  
  35.     Shar-formatted output is to StdOut (e.g., via redirection at the
  36.     DOS command line).  No check is made for an output file's existence
  37.     (naturally).  Output is standard DOS text file (e.g., CR/LF line
  38.     endings).
  39.  
  40.     No tests are made to filter out control characters, etc., and target
  41.     member files are treated as text files.  I would NOT suggest using
  42.     this shar on anything but pure Ascii text files!
  43.  
  44.     Any error msgs will go to StdOut (yep, the file you're creating!).
  45.     Sorry 'bout that .. don't wanna mess with a StdErr output routine
  46.     at this time.  Maybe later.
  47.  
  48.  
  49.   During extraction:
  50.  
  51.     You can wildcard the extracting.  (E.g., if you have FOO1.SHR and
  52.     FOO2.SHR, just enter "TOADSHAR FOO*" and both files will be unshared.)
  53.  
  54.     Existing files will NOT be overwritten!  You'll get a warning message,
  55.     and shar will continue to work its way through the remaining shar file
  56.     members (if any).
  57.  
  58.     No tests are made (to date) to replicate sed or sh errorchecking
  59.     (e.g., the simplistic character count).
  60.  
  61.     Some sed/sh "echo" commands are echoed to StdOut during extraction.
  62.  
  63.   I've tested TOADSHAR on Unix and MS-DOS .shar files created with various
  64.   switch settings .. seems to work ok.
  65.  
  66.   This sucker isn't ALL it could be yet .. could use more file read/write
  67.   error trapping, more sophisticated sh-like testing (char counts,
  68.   file overwriting, etc.) .. but it'll do for now.
  69.  
  70.   Credits:
  71.     Fancy dynamic arrays of FindFirst and FindNext SearchRecs,
  72.     thanks to a hack of:
  73.  
  74.     "Linked list modules from LINKLIST.PAS"
  75.     Copyright (c) 1985 by Alan D. Hull
  76.  
  77.     Boyer-Moore string searching (credits, source in POSBM.ASM)
  78.  
  79.     Released to the public domain.
  80.     Constraints:  Do NOT distribute without source and documentation.
  81.                   Do NOT remove credits.
  82.  
  83.     David P Kirschbaum
  84.     Toad Hall
  85.     kirsch@braggvax.ARPA
  86.     (or maybe kirsch%braggvax.ARPA@cacfs.army.mil)
  87.     (919) 868-3471 voice/data
  88.  
  89.     v1.0  Original release
  90.     v1.1  Added:
  91.             new posBM and posCH POS() replacements.
  92.             StdErr message output.
  93. }
  94.  
  95. {$B-}  {shortcut Boolean logic}
  96. {$D-}  {No debug info}
  97. {$F-}  {No far calls}
  98. {$L-}  {No local symbol info}
  99. {$R-}  {No range checking}
  100. {$S-}  {No stack checking .. taking a chance on this one
  101.         for systems with VERY limited memory ..
  102.         You don't like it?  Recompile it.}
  103. {$V-}  {Relaxed VAR string parm testing}
  104.  
  105. { DEFINE Debug}  {enable for some debug displays, file overwriting, etc.}
  106.  
  107. Uses Dos;  {for the Find First/Find Next stuff}
  108.  
  109. TYPE
  110.   Str20 = STRING[20];
  111.   Str80 = STRING[80];
  112.  
  113. CONST
  114.   QUOTE = #39;                      {single-quotation mark/apostrophe char}
  115.   MAXARGS = 20;                     {change as you like}
  116.   VERSION = 'v1.1';
  117.   CRLF : ARRAY[1..2] OF CHAR = #$0D#$0A;  {v1.1}
  118.  
  119. VAR
  120.   argv, argc : Byte;
  121.   Args : ARRAY[1..MAXARGS]          {array of cmdline parms}
  122.            OF PathStr;              {STRING[79]}
  123.  
  124.   InFile : TEXT;
  125.  
  126.  
  127. {
  128.  
  129. SearchRec, DirStr, NameStr, ExtStr are declared in the Dos unit.
  130.  As a reminder:
  131.  
  132.  TYPE SearchRec = RECORD
  133.                     fill : ARRAY[1..21] OF Byte;
  134.                     attr : Byte;
  135.                     time : longint;
  136.                     size : longint;
  137.                     Name : STRING[12];
  138.                   END;
  139. }
  140.     SrchRec : SearchRec;
  141.  
  142.     Dir : DirStr;                     {STRING[79]}
  143.     Name: NameStr;                    {STRING[8]}
  144.     Ext : ExtStr;                     {STRING[4]}
  145.  
  146. CONST
  147.  
  148.   {The shar file header (picked from a handy Unix speciman).
  149.   This array of array of chars is a kludge, I know .. but it was the
  150.   simplest/fastest way to collect one big hunk of characters for output.
  151.   Now if I wanted to add my block read/writes .. but then we wouldn't
  152.   have a nice neat TEXT file, would we?
  153.   }
  154. (*
  155.   Hdr1 : ARRAY[1..26] OF CHAR = '#    This is a shell archive.';
  156.   Hdr2 : ARRAY[1..53] OF CHAR =
  157. '#    Remove everything above and including the cut line.';
  158.   Hdr3 : ARRAY[1..43] OF CHAR =
  159. '#    Then run the rest of the file through sh.';
  160.   Hdr4 : ARRAY[1..57] OF CHAR =
  161. '#----cut here-----cut here-----cut here-----cut here----#';
  162.   Hdr5 : ARRAY[1..9] OF CHAR = '#!/bin/sh';
  163.   Hdr6 : ARRAY[1..25] OF CHAR = '# shar:    Shell Archiver';
  164.   Hdr7 : ARRAY[1..48] OF CHAR =
  165. '#    Run the following text with /bin/sh to create:';
  166. *)
  167.  
  168.   NR_HDRLINES = 4;
  169.   Hdr : ARRAY[1..NR_HDRLINES] OF Str80 =
  170.   (
  171. '#  This is a shell archive.'#$0D#$0A'# Remove everything above and including the cut ',
  172. 'line.'#$0D#$0A'# Then run the rest of the file through sh.'#$0D#$0A'#----cut here-----cut here',
  173. '-----cut here-----cut here----#'#$0D#$0A'#!/bin/sh'#$0D#$0A'# shar:    Shell Archiver',
  174. #$0D#$0A'# Run the following text with /bin/sh to create:'#$0D#$0A
  175.   );
  176.  
  177.  
  178. {Load our posBM and posCH modules}
  179.  
  180. {$F+}
  181. {$L POSBM}
  182.  
  183. FUNCTION posBM(Pat,Str : STRING) : BYTE; EXTERNAL;
  184.  
  185. {$L POSCH}
  186. FUNCTION posCH(Ch : CHAR; S : STRING) : BYTE; EXTERNAL;
  187.  
  188. {And our StdErr procedure}
  189. {$L STDERR}
  190.  
  191. PROCEDURE Write_StdErr(S : STRING); EXTERNAL;
  192. {$F-}
  193.  
  194. {
  195. (Linked list modules from LINKLIST.PAS)
  196.  Copyright (c) 1985 by Alan D. Hull
  197.  TURBO LinkList modules and descriptions are hereby donated to
  198.  the public domain. They may be included  in  any  other  free
  199.  software  without  royalties  to  the  author. TURBO LinkList
  200.  procedures,  descriptions  and/or  declarations  may  not  be
  201.  included  in  whole  or  in part in any program, function, or
  202.  package   sold  for  commercial  gain,  without  the  express
  203.  permission of the author.
  204.  
  205.  Thanks, Alan .. gee, 1985 .. sigh ..
  206. }
  207.  
  208.  
  209. TYPE
  210. { We don't really NEED the entire SearchRec saved (from a FindFirst
  211.   or FindNext) .. but I'm keeping it handy for now.
  212.   Actually, all we need is the Dir and SearchRec.Name (for opening
  213.   input files later).
  214. }
  215.   SrchRecPtr = ^node;
  216.  
  217.   node = RECORD                         { this is the linked list node }
  218.     flink,
  219.     blink   : SrchRecPtr;
  220.     SrchRec : SearchRec;                { map in a data record }
  221.     Dir     : DirStr;                   {remember the directory also}
  222.   END;
  223.  
  224. VAR
  225.   head, tail, curr, Temp  :   SrchRecPtr;
  226.  
  227.  
  228.  
  229. PROCEDURE Allocate_Node( VAR node_ptr: SrchRecPtr);
  230.   {  Allocate a node of a doubly-linked list  }
  231.   BEGIN
  232.     NEW (node_ptr);                     { get a new block of memory }
  233.     node_ptr^.flink := NIL;             { make sure it doesn't point to }
  234.     node_ptr^.blink := NIL;             { any other nodes yet. }
  235.   END;
  236.  
  237.  
  238. PROCEDURE Add_After_Node (VAR head, tail, current, newp: SrchRecPtr);
  239.   {  Add the node to the linked list
  240.      head    - A pointer to the first node in the linked list
  241.      tail    - A pointer to the last node in the linked list
  242.      current - A pointer to the node in the list that the new node
  243.                is to be added after.
  244.      newp    - A pointer to the node to be added to the linked list.
  245.                (Couldn't use NEW since it's a reserved word in Pascal)
  246.   }
  247.   VAR  next: SrchRecPtr;
  248. {  1.  The list is empty, head, tail, and current will point to newp.
  249.    2.  We are adding past the end of the list.  Redirect tail.
  250.    3.  Adding at some point other that after the tail.
  251.    4.  Point current to the new node.
  252. }
  253.  
  254.   BEGIN
  255.     IF (current = NIL) THEN BEGIN    { 1 }
  256.       head := newp;
  257.       tail := newp;
  258.     END
  259.     ELSE BEGIN
  260.       IF (current = tail) THEN BEGIN    { 2 }
  261.         current^.flink := newp;
  262.         newp^.blink := current;
  263.         newp^.flink := NIL;
  264.         tail := newp;
  265.       END
  266.       ELSE BEGIN    { 3 }
  267.         next := current^.flink;
  268.         newp^.flink := next;
  269.         newp^.blink := current;
  270.         next^.blink := newp;
  271.         current^.flink := newp;
  272.       END
  273.     END;
  274.     current := newp;    { 4 }
  275.   END;  {Add_After_Node}
  276.  
  277.  
  278. (*
  279. {To remove a node:   looks like this.  We don't DO this .. just left
  280.  Alan's comments/code for your edification.
  281.  
  282.  1. before removing the current node from list, we need to store the
  283.     pointer to the previous node, so that we can step "back" a node to
  284.     continue processing thru the list.
  285.  2. Restore the pointer from item 1 as the current node
  286. }
  287.     curr := head;
  288.     IF curr <> NIL THEN BEGIN
  289.       temp := curr^.blink;         { save pointer to prev. node }
  290.       Remove_Node (head, tail, curr);
  291.       curr := temp;                { reassign to maintain continuity }
  292.       curr := curr^.flink;
  293. {or}  curr := temp^.flink;
  294.     END;
  295. *)
  296.  
  297. { ***** End of LINKLIST-related stuff ***** }
  298.  
  299. FUNCTION ItoS(i : INTEGER) : Str20;
  300.   VAR  S : Str20;
  301.   BEGIN
  302.     STR(i,S);
  303.     ItoS := S;
  304.   END;  {of ItoS}
  305.  
  306.  
  307. PROCEDURE Usage;
  308.   BEGIN
  309.     Writeln('TOADSHAR public domain shar/unshar utility ', VERSION);
  310.     Writeln;
  311.     Writeln('Usage:  shar [-u] [file1]..[filen] [>output.shr]');
  312.     Writeln;
  313.     Writeln('Where file1..filen can be up to 20 MS-DOS path\filenames');
  314.     Writeln('(wildcards permitted).');
  315.     Writeln('Output is to StdOut (e.g., redirectable).');
  316.     Writeln;
  317.     Writeln('-u     Unshar (extract) members from shar file(s).');
  318.     Writeln('       Yes, the -u MUST be the first command line parameter!');
  319.     Writeln('       shar filenames can be full DOS paths,');
  320.     Writeln('       with a default file type of ".SHR" (added if required).');
  321.     Writeln('       Extracted file will NOT be written if a file of that name');
  322.     Writeln('       exists on the current drive:\directory.');
  323.     Writeln;
  324.     Writeln('Courtesy of David Kirschbaum, Toad Hall');
  325.     Halt(1);
  326.   END;  {of Usage}
  327.  
  328.  
  329. FUNCTION Uc (S : String) : String;
  330.   {v1.3 Returns S uppercased}
  331.   BEGIN
  332. Inline(
  333.   $31/$C0/       {  xor   ax,ax}
  334.   $8A/$86/>S/    {  mov   al,>S[bp]  ;snarf the length}
  335.   $09/$C0/       {  or    ax,ax      ;0 length?}
  336.   $74/$18/       {  jz    Exit       ;yep, exit}
  337.  
  338.   $89/$C1/       {  mov   cx,ax      ;loop counter}
  339.   $BA/$61/$20/   {  mov   dx,$2061   ;DL='a',DH=$20}
  340.   $31/$F6/       {  xor   si,si}
  341.                  {L1:}
  342.   $46/           {  inc   si       ;next char}
  343.   $36/           {  SS:}
  344.   $8A/$82/>S/    {  mov   al,>S[bp][si]  ;snarf the char}
  345.   $38/$D0/       {  cmp   al,dl}
  346.   $72/$05/       {  jb    S1       ;already uppercase}
  347.   $36/           {  SS:}
  348.   $28/$B2/>S/    {  sub  >S[bp][si],dh   ;uppercase it}
  349.                  {S1:}
  350.   $E2/$EF);      {  loop  L1}
  351.                  {Exit:}
  352.  
  353.     Uc := S;    {return the function}
  354.   END;  {of Uc}
  355.  
  356.  
  357. PROCEDURE Strip(Ch : CHAR; VAR S : String);
  358.   {Strip any Ch chars from S}
  359.   VAR  p : INTEGER;
  360.   BEGIN
  361.     Repeat
  362.       p := posCh(Ch,S);             {v1.1 any there?}
  363.       IF p <> 0 THEN Delete(S,p,1); {yep, gobble them}
  364.     Until p = 0;
  365.   END;  {of Strip}
  366.  
  367.  
  368. FUNCTION Bracketed(S : String) : String;
  369.   {return string in brackets}
  370.   BEGIN
  371.     Bracketed := '[' + S + ']';
  372.   END;  {of Bracketed}
  373.  
  374.  
  375. PROCEDURE Get_Args;
  376.   {v1.3 process command line for all target filenames.
  377.         Move them into an array of PathStrs.
  378.   }
  379.   BEGIN
  380.     argc := ParamCount;
  381.     IF (argc = 0)                       {no parms at all}
  382.     OR (argc > MAXARGS)                 {or more than we can handle}
  383.     THEN Usage;                         {display help, die}
  384.  
  385.     FOR argv := 1 TO argc DO
  386.       Args[argv] := Uc(ParamStr(argv));   {snarf parm, (uppercased)}
  387.     Args[SUCC(argc)] := '';               {double-insure no overruns}
  388.  
  389.   END;  {of Get_Args}
  390.  
  391.  
  392. PROCEDURE Find_All;
  393.   {Work FindFirst/FindNext for each Arg name.
  394.    When FindFirst or FindNext fails, start on the next argv.
  395.  
  396.    Remember, StdOut may be creating a file that meets the argument.
  397.    However .. since that StdOut file will (should?) be 0 size
  398.    (until DOS closes it) .. we can trap that easily enough.
  399.    Side-effect is:  we won't be able to include any 0-sized files ..
  400.    but who wants to do that anyway?
  401.   }
  402.     VAR  Ok  : BOOLEAN;
  403.  
  404.     PROCEDURE Make_Node;
  405.       BEGIN
  406.         IF SrchRec.size <> 0 THEN BEGIN {only for non-empty files.
  407.                                          This also stops us from
  408.                                          picking up the StdOut file!}
  409.           Allocate_Node(temp);
  410.           Temp^.Dir := Dir;
  411.           Temp^.SrchRec := SrchRec;     {move in the whole search record}
  412.           Add_After_Node (head,tail,curr,Temp);
  413.         END;
  414.       END;  {of Make_Node}
  415.  
  416.   BEGIN  {Find_All}
  417.     head := NIL;               {init our filename pointer linked list}
  418.     tail := NIL;
  419.     curr := NIL;
  420.     temp := NIL;
  421.  
  422.     FOR argv := 1 TO argc DO BEGIN          {do all the args}
  423.  
  424.       FSplit(Args[argv], Dir, Name, Ext);   {split up the new name}
  425.       Findfirst(Args[argv],                 {full name}
  426.                 READONLY OR ARCHIVE,
  427.                 SrchRec);
  428.  
  429.       IF DosError = 0 THEN BEGIN            {FindFirst succeeded}
  430.         Make_Node;                          {save the FindFirst name}
  431.         Repeat
  432.           FindNext(SrchRec);                {find any more}
  433.           Ok := (DosError = 0);
  434.           IF Ok THEN Make_Node;             {save the FindNext name}
  435.         UNTIL NOT Ok;                       {until FindNext failed}
  436.       END;  {if FindFirst succeeded}
  437.     END;  {argv loop}
  438.   END;  {of Find_All}
  439.  
  440.  
  441. FUNCTION Exists (FName : PathStr) : BOOLEAN;
  442.   {Return TRUE if FName exists}
  443.   VAR F : FILE;
  444.   BEGIN
  445.     Assign(F,FName);
  446.     {$I-}  Reset(F);  {$I+}
  447.     IF IoResult = 0 THEN BEGIN          {exists}
  448.       Exists := TRUE;                   {return function TRUE}
  449.       Close(F);                         {be neat}
  450.     END
  451.     ELSE Exists := FALSE;               {return function FALSE}
  452.   END;  {of Exists}
  453.  
  454.  
  455. PROCEDURE Show_TargetNames;
  456.   VAR S : Str80;
  457.   BEGIN
  458.     Write_StdErr('shar: target files: ');     {v1.1 A little informative..}
  459.     curr := head;                       {..wildcard info}
  460.     S := '';                            {v1.1 clear output string}
  461.     WHILE curr <> NIL DO BEGIN
  462.       S := S + curr^.SrchRec.Name;      {build a string of names}
  463.       curr := curr^.flink;              {bump to next name}
  464.  
  465.       IF LENGTH(S) > 60 THEN BEGIN      {string's long enough...}
  466.         Write_StdErr(S + CRLF);         {...so display the names}
  467.         S := '';                        {...and clear the string}
  468.       END
  469.       ELSE IF curr <> NIL               {isn't last name...}
  470.       THEN S := S + ', ';               {..so separate names neatly}
  471.     END;
  472.  
  473.     IF S <> '' THEN Write_StdErr(S + CRLF);  {v1.1 display last partial string}
  474.   END;  {of Show_TargetNames}
  475.  
  476.  
  477. PROCEDURE UnShar;
  478.   {-u parm on cmdline.  We may have filename(s)
  479.    or wildcards starting at Args[2].
  480.    Shift all args down one, expand wildcards, etc.
  481.    If no file type, use '.shr'.
  482.    If no Args[2], use '*.shr'
  483.   }
  484.   VAR
  485.     S : String;
  486.     OutFile : TEXT;
  487.     p,
  488.     line : word;
  489.     slen : BYTE Absolute S;
  490.     SharEof : BOOLEAN;
  491.     Ch : CHAR;
  492.  
  493.  
  494.   FUNCTION Word(S : String; p : INTEGER; Delim : CHAR) : Str20;
  495.     {Returns the next word starting at S[p],
  496.      and ending at the char Delim (or String end)
  497.     }
  498.     VAR Ch : CHAR;
  499.     BEGIN
  500.       Delete(S,1,PRED(p));              {gobble leading chars}
  501.       IF S = '' THEN BEGIN
  502.         Word := S;                      {Return function}
  503.         Exit;
  504.       END;
  505.  
  506.       WHILE (LENGTH(S) > 0)             {while we have a string}
  507.       AND (S[1] <= #$20) DO             {and leading char is ctrl}
  508.         Delete(S,1,1);                  {gobble it (really after spaces
  509.                                          and tabs)}
  510.  
  511.       p := posCH(Delim,S);              {v1.1 find delimiter}
  512.       IF p = 0 THEN Word := S           {no delimiter,
  513.                                          return remaining string}
  514.       ELSE Word := Copy(S,1,PRED(p));   {return up to but not including
  515.                                          delimiter}
  516.     END;  {of Word}
  517.  
  518.  
  519.   PROCEDURE Die(Msg : String);
  520.     BEGIN
  521.       Write_StdErr('shar: ' + Msg
  522.                    + ' Line: ' + ItoS(line) + CRLF);  {v1.1}
  523.       {$I-}  Close(OutFile); {$I+}      {in case not opened}
  524.       IF IoResult <> 0 THEN;            {we don't care}
  525.       SharEof := TRUE;                  {post boolean}
  526.     END;  {of Die}
  527.  
  528.  
  529.   PROCEDURE  ReadLn_Eof;
  530.     {Halts us if we hit input file EOF}
  531.     BEGIN
  532.       S := '';                          {insure S is cleared}
  533.       IF NOT SharEof THEN SharEof := Eof(InFile);
  534.       IF NOT SharEof THEN BEGIN
  535.         ReadLn(InFile,S);
  536.         Inc(line);
  537.       END;
  538.     END;
  539.  
  540.  
  541.   PROCEDURE Extract_Member;
  542.     {Extracts a single member (down to SHAR_EOF or whatever
  543.      We should be at a line that looks like this:
  544.           cat << \SHAR_EOF > test1.doc
  545.      or maybe 2 lines (if some switches like -s or -a were used):
  546.           echo shar: extracting test1.doc
  547.           sed 's/^XX//' << \SHAR_EOF > test1.doc
  548.      or   sed 's/^X//' > unshar.c << '/'
  549.      or   sed 's/^X//' > makeguide << 'EOF'
  550.  
  551.      It's not LIKELY SHAR_EOF would change between members,
  552.      but we'll check every time anyway.
  553.     }
  554.     VAR
  555.       OutName : PathStr;
  556.       EofStr : STRING[20];                 {for SHAR_EOF or whatever}
  557.       LeadChars : STRING[5];               {guessing as to min length}
  558.       leadlen  : BYTE absolute LeadChars;  {nr of leadchars}
  559.       DoOutPut,
  560.       Ok : BOOLEAN;
  561.  
  562.  
  563.     PROCEDURE Check_LeadChars;
  564.       {Extract_Member subroutine
  565.        In case some more switches were engaged,
  566.        and we get a line like this:
  567.            sed 's/^XX//' << \SHAR_EOF > test1.doc
  568.        or  sed 's/^X//' > unshar.c << '/'
  569.        or  sed 's/^X//' > makeguide << 'EOF'
  570.       }
  571.       VAR p1,p2 : INTEGER;
  572.       BEGIN
  573.         LeadChars := '';                      {assume no leading chars}
  574.         IF Word(S,1,' ') <> 'sed' THEN Exit;  {only sed does leading chars}
  575.  
  576.         p1 := posBM('s/^',S);                 {v1.1}
  577.         IF p1 = 0 THEN Exit;                  {no leading chars}
  578.  
  579.         Inc(p1,3);                            {bump past 's/^'}
  580.         p2 := posBM('//',S);                  {v1.1 find end of token}
  581.         IF p2 > p1                            {gotta have at least 1}
  582.         THEN LeadChars := Copy (S,p1,p2-p1);  {copy leading chars}
  583.  
  584.         IF LeadChars = ''                     {last test}
  585.         THEN Die('s/ Leading char error');    {bad format, SharEof TRUE}
  586.  
  587.       END;  {of Check_LeadChars}
  588.  
  589.  
  590.     PROCEDURE Get_SharEof;
  591.       {Extract_Members subroutine.
  592.        Find the "\SHAR_EOF > ", save it.
  593.        Again, we're working a command line like:
  594.               cat << \SHAR_EOF > test1.doc
  595.        or     sed 's/^XX//' << \SHAR_EOF > test1.doc
  596.        or     sed 's/^X//' > unshar.c << '/'
  597.        or     sed 's/^X//' > makeguide <<'EOF'
  598.       }
  599.       BEGIN
  600.         EofStr := '';                         {clear it}
  601.         p := posBM('<<', S);                  {v1.1 find the SHAR_EOF token}
  602.         IF p <> 0 THEN BEGIN                  {ok, found it}
  603.           Inc(p,2);                           {skip past '<<'}
  604.           EofStr := Word(S,p,' ');            {get next word}
  605.           IF EofStr <> '' THEN BEGIN          {we got something!}
  606.             Case EofStr[1] OF
  607.               QUOTE : Ch := QUOTE;            {extract between quotes}
  608.               '\'   : Ch := ' ';              {extract up to space}
  609.               ELSE  Ch := #0;                 {an error}
  610.             END;  {case}
  611.             IF Ch = #0 THEN EofStr := ''
  612.             ELSE EofStr := Word(EofStr,2,Ch);  {extract word up to delimiter}
  613.           END;
  614.         END;  {if SHAR_EOF Token}
  615.  
  616.         IF EofStr = '' THEN Die('No SHAR_EOF'); {SharEof TRUE}
  617.  
  618.       END;  {of Get_SharEof}
  619.  
  620.  
  621.     PROCEDURE Get_OutName;
  622.       {Extracts output filename from cat or sed cmdline.
  623.        Again, we're working a command line like:
  624.               cat << \SHAR_EOF > test1.doc
  625.        or     sed 's/^XX//' << \SHAR_EOF > test1.doc
  626.        or     sed 's/^X//' > unshar.c << '/'
  627.        or     sed 's/^X//' > makeguide << 'EOF'
  628.        Note:  The name could be quoted!
  629.        v1.1 Warning msg output is to StdOut, but that's ok ..
  630.        User shouldn't be redirecting on UnSharing.
  631.       }
  632.       VAR
  633.         OutN1 : PathStr;
  634.         S1 : Str80;
  635.       BEGIN
  636.         OutName := '';                      {clear it}
  637.         p := posCH('>', S);                 {v1.1 find the filename output char}
  638.         IF p < 3 Then BEGIN                 {should be deep in the cmdline}
  639.           Die('Format Error');              {SharEof TRUE}
  640.           Exit;
  641.         END;
  642.  
  643.         OutName := Word(S,SUCC(p),' ');     {Extract output filename}
  644.         Strip(QUOTE,OutName);               {gobble any quotation marks}
  645.         Strip('"',OutName);                 {these too}
  646.  
  647.         IF OutName <> '' THEN BEGIN         {some rudimentary parsing}
  648.  
  649.           OutName := Uc(OutName);           {uppercase it now}
  650.  
  651.           OutN1 := OutName;                 {fiddle local name}
  652.           IF OutN1[1] = '.' THEN BEGIN      {Leading periods is bogus}
  653.             Write_StdErr('shar:  WARNING!  Replacing period in filename: '
  654.                          + Bracketed(OutN1) + CRLF);  {v1.1}
  655.             OutN1[1] := '_';                {replace with something else}
  656.           END;
  657.           FSplit(OutN1, Dir, Name, Ext);    {split up the new name}
  658.           IF Dir <> '' THEN BEGIN           {better be empty!}
  659.             Write_StdErr('shar:  WARNING!  Ignoring Output name path: '
  660.                          + Bracketed(Dir) + CRLF);  {v1.1}
  661.           END;
  662.           OutN1 := Name + Ext;              {build new name after the split}
  663.           IF OutN1 <> OutName THEN BEGIN
  664.             Write_StdErr('shar:  WARNING!  Output name amended from '
  665.                     + Bracketed(OutName) + ' to ' + Bracketed(OutN1)
  666.                     + CRLF );               {v1.1}
  667.             OutName := OutN1;
  668.           END;
  669.         END;                                {hopefully it'll be legal}
  670.  
  671.         IF OutName = ''                     {couldn't parse output filename}
  672.         THEN Die('Missing filename');       {SharEof TRUE}
  673.  
  674.       END;  {of Get_OutName}
  675.  
  676.  
  677.     PROCEDURE Process_Member;
  678.       {Extract_Member subroutine.
  679.        We're now reading the shar file's data.
  680.        Strip lead chars if necessary.
  681.        Stop at SHAR_EOF (in EofStr).
  682.       }
  683.       BEGIN
  684.  
  685.         ReadLn_Eof;                            {start the read/write}
  686.         WHILE (NOT SharEof)                    {not physical EOF}
  687.           AND (S <> EofStr)                    {and not member SHAR_EOF}
  688.         DO BEGIN
  689.           IF leadlen <> 0 THEN BEGIN           {we have leading chars}
  690.             IF COPY(S,1,leadlen) <> LeadChars  {a fatal error}
  691.             THEN BEGIN
  692.               Die('Missing LeadChars: ' + Bracketed(S));
  693.               Exit;                            {quit now}
  694.             END;
  695.  
  696.             Delete(S,1,leadlen);           {gobble leading chars}
  697.           END;
  698.  
  699.           IF DoOutPut                      {we're writing an output file}
  700.           THEN Writeln(OutFile,S);         {write out the string}
  701.           ReadLn_Eof;                      {new S}
  702.         END;  {wend}
  703.                                            {S = 'SHAR_EOF' or physical EOF}
  704.       END;  {of Process_Member}
  705.  
  706.  
  707.     BEGIN  {Extract_Member (UnShar subroutine)}
  708.  
  709.       {We're now processing the first command lines for this member.}
  710.  
  711.       Check_LeadChars;                  {check for just 'cat ' cmd or
  712.                                          maybe the more complicated
  713.                                          "sed 's/^XX//'" (lead chars).
  714.                                          Process accordingly.}
  715.       Get_SharEof;                      {Extract "\SHAR_EOF > " from cmdline
  716.                                          as EofStr.}
  717.  
  718.       Get_OutName;                      {Extract '> filename' from cmdline
  719.                                          as OutName.}
  720.       IF SharEof THEN Exit;             {failed during cmdline processing}
  721.  
  722.       DoOutput := TRUE;                 {Assume we will output}
  723.       Write_StdErr('  Member: ' + OutName + CRLF);  {v1.1 display member file}
  724.  
  725. {$IFNDEF Debug}
  726.       IF Exists(OutName)                {no overwriting!}
  727.       THEN BEGIN
  728.         Die('File Exists: ' + Bracketed(OutName));
  729.         SharEof := FALSE;               {but keep working the .shr file}
  730.         DoOutput := FALSE;              {process, but no output}
  731.       END;
  732. {$ENDIF}
  733.  
  734.       IF DoOutPut THEN BEGIN            {we're outputting a member}
  735.         Assign(OutFile,OutName);        {so gotta create its file}
  736.         {$I-} ReWrite(OutFile);  {$I+}
  737.         IF IoResult <> 0                {create failed somehow}
  738.         THEN BEGIN
  739.           Die('Create error: ' + Bracketed(OutName));
  740.           SharEof := FALSE;             {but keep working the .shr file}
  741.           DoOutput := FALSE;            {but no attempts to output}
  742.         END;
  743.       END;
  744.  
  745.       Process_Member;                   {process the file down to SHAR_EOF}
  746.  
  747.       {Done with this member.  Current S should be SHAR_EOF}
  748.  
  749.       IF DoOutPut THEN BEGIN
  750.         {$I-}  Close(OutFile);  {$I+}   {just in case of a problem}
  751.         IF IoResult <> 0 THEN;          {we don't care}
  752.       END;
  753.     END;  {of Extract_Member}
  754.  
  755.  
  756.   PROCEDURE Sh;
  757.     {Crudely do what Sh does ..
  758.       " and blindly go where no man has dared before .. "
  759.      (for 'blindly', read 'ignorantly')
  760.     }
  761.     CONST
  762.       Sh_Words : STRING[46] =
  763.         'if then else fi test echo export cat sed exit ';
  764.         {Why this overwhelming urge to include "fee fi fo fum"?}
  765.     VAR
  766.       W : Str20;
  767.       IfFlag,                 {Not fully implemented yet..}
  768.       ThenFlag,               {..but I didn't wanna write..}
  769.       ElseFlag : BOOLEAN;     {..a full sh parser!}
  770.  
  771.     BEGIN
  772.       IfFlag := FALSE;
  773.       ThenFlag := FALSE;
  774.       ElseFlag := FALSE;
  775.  
  776.       WHILE NOT SharEof DO BEGIN
  777.  
  778.         S[1] := #0;                     {physically clear first char}
  779.         S := '';                        {clear the string}
  780.  
  781.         While  (                        {a la c ...}
  782.                (S = '')                 {gobble blank lines}
  783.             OR (S[1] IN ['#',':'])      {and sh comments}
  784.                )
  785.            AND NOT SharEof              {but not physical EOF}
  786.         DO ReadLn_Eof;                  {work through header, junk}
  787.  
  788.         IF SharEof THEN Exit;           {physical EOF, done}
  789.  
  790.         W := Word(S,1,' ') + ' ';       {S's first word, plus a space}
  791.  
  792.  
  793. {a reminder:
  794.   Sh_Words : STRING[46] =
  795.     'if then else fi test echo export cat sed exit ';
  796.      1  4    9    14 17   22   27     34  38  42
  797.  
  798. Above construct is tighter than an array of words and looping
  799. through the array, testing for membership!
  800. }
  801.         p := posBM( W , Sh_Words);      {v1.1 is it a sh cmd?}
  802.         CASE p OF
  803.           0 : Write_StdErr('Unknown sh cmd: ' + Bracketed(S)
  804.                            + CRLF );  {v1.1}
  805.           1 : BEGIN                     {if}  {only the ThenFlag matters...}
  806. {stubbed        IfFlag := TRUE; }             {...for now}
  807.                 ThenFlag := FALSE;
  808. {stubbed        ElseFlag := FALSE; }
  809.               END;
  810.           4 : BEGIN                     {then}
  811.                 ThenFlag := TRUE;
  812. {stubbed        ElseFlag := FALSE; }
  813.               END;
  814.           9 : BEGIN                     {else}
  815. {stubbed        ElseFlag := TRUE; }
  816.                 ThenFlag := FALSE;
  817.               END;
  818.          14 : BEGIN                     {fi}
  819. {stubbed        IfFlag := FALSE; }
  820.                 ThenFlag := FALSE;
  821. {stubbed        ElseFlag := FALSE; }
  822.               END;
  823.          17,27 : ;                      {gobble test's, export's}
  824.          22 : BEGIN                     {echo}
  825.                 IF NOT ThenFlag         {All then's seem to be bad news ..}
  826.                 THEN BEGIN              {..and who wants to hear bad news?}
  827.                   Delete(S,1,5);        {gobble the 'echo '}
  828.                   Write_StdErr(S + CRLF); {v1.1 Display string to StdErr}
  829.                 END;
  830.               END;
  831.          34,                            {cat,}
  832.          38 : Extract_Member;           {sed: write out the member}
  833.          42 : SharEof := TRUE;          {exit: finished}
  834.         END;  {case}
  835.       END;  {While NOT SharEof}
  836.  
  837.     END;  {of Sh}
  838.  
  839.  
  840.  
  841.   PROCEDURE Unshar_File;
  842.     VAR FName : PathStr;
  843.     BEGIN
  844.       FName := curr^.Dir + curr^.SrchRec.Name;  {full filename}
  845.       Write_StdErr('shar: processing ' + FName + CRLF);
  846.  
  847.       Assign(InFile, FName);
  848.       Reset(InFile);                    {open input file}
  849.       SharEof := FALSE;                 {init file Eof flag}
  850.       line := 0;                        {Init line counter}
  851.  
  852.       { First look for the header start.  Could be text or other
  853.         junk from mailers, etc.}
  854.  
  855.       Repeat
  856.         Readln_Eof
  857.       Until SharEof                     {hit physical .shr EOF}
  858.       OR ( (slen <> 0) AND (S[1] IN ['#',':']) );  {or we have a header line}
  859.  
  860.       Sh;                               {process sh commands}
  861.  
  862.       Close(InFile);                    {neaten up}
  863.  
  864.     END;  {of UnShar_File}
  865.  
  866.  
  867.  
  868.  
  869.   BEGIN  {UnShar}
  870.  
  871.     IF argc = 1 THEN BEGIN              {just '-u', no names}
  872.       Args[1] := '*.SHR';               {default}
  873.       Args[2] := '';                    {insure no overruns}
  874.     END
  875.     ELSE BEGIN                          {at least one target filename}
  876.       Dec(argc);                        {discard first arg ('-u')}
  877.       FOR argv := 1 TO argc DO          {do argc-1 shifts}
  878.         Args[argv] := Args[SUCC(argv)]; {Shift all args down one}
  879.       Args[SUCC(argc)] := '';           {blank to insure no overruns}
  880.  
  881.       FOR argv := 1 TO argc DO BEGIN    {expand to .SHR if required}
  882.         IF posCH('.',Args[argv]) = 0    {v1.1 no file.typ separator}
  883.         THEN Args[argv] := Args[argv] + '.SHR';  {so force it}
  884.       END;
  885.     END;
  886.  
  887.     Find_All;                           {create array of target files}
  888.  
  889. (*
  890.     Write_StdErr('target files: ');     {v1.1 A little informative..}
  891.     curr := head;                       {..wildcard info}
  892.     S := '';                            {v1.1 clear output string}
  893.     WHILE curr <> NIL DO BEGIN
  894.       S := S + curr^.SrchRec.Name;      {build a string of names}
  895.       curr := curr^.flink;              {bump to next name}
  896.  
  897.       IF LENGTH(S) > 60 THEN BEGIN      {string's long enough...}
  898.         Write_StdErr(S + CRLF);         {...so display the names}
  899.         S := '';                        {...and clear the string}
  900.       END
  901.       ELSE IF curr <> NIL               {isn't last name...}
  902.       THEN S := S + ', ';               {..so separate names neatly}
  903.     END;
  904.  
  905.     IF S <> '' THEN Write_StdErr(S + CRLF);  {v1.1 display last partial string}
  906. *)
  907.     Show_TargetNames;                   {v1.1}
  908.     curr := head;                       {start with first filename}
  909.     WHILE curr <> NIL DO BEGIN
  910.       UnShar_File;                      {do them all}
  911.       curr := curr^.flink;              {next file ptr}
  912.     END;
  913.  
  914.   END;  {of UnShar}
  915.  
  916.  
  917. PROCEDURE Shar;
  918.   {We're creating a shar file to StdOut}
  919.   VAR
  920.     FName : PathStr;
  921.     S : String;
  922.     err : INTEGER;
  923.  
  924.   PROCEDURE Write_Header;
  925.     {Output shar header and filenames}
  926.     VAR i : INTEGER;
  927.     BEGIN
  928. (*
  929.      Writeln(Hdr1);                   {7 separate arrays of char ...}
  930.      Writeln(Hdr2);                   {... what a kludge ...}
  931.      Writeln(Hdr3);
  932.      Writeln(Hdr4);
  933.      Writeln(Hdr5);
  934.      Writeln(Hdr6);
  935.      Writeln(Hdr7);
  936. *)
  937.      FOR i := 1 TO NR_HDRLINES DO
  938.        Write(Hdr[i]);
  939.  
  940. { The rest of the header oughtta look like this:
  941. #    test1.doc
  942. #    test2.doc
  943. #    test3.doc
  944. # This archive created: Mon Apr 17 11:30:47 1989
  945. }
  946.       curr := head;                     {first filename}
  947.       WHILE curr <> NIL DO
  948.       WITH curr^ DO BEGIN
  949.         WriteLn( '#',^I,SrchRec.Name);  {list them all, neatly}
  950.         curr := curr^.flink;            {next filename}
  951.       END;
  952.       {I don't feel like hacking all the code it takes
  953.       to add the pretty date/time line .. YOU do it!
  954.       }
  955.       Writeln('# This archive created: Mon Apr 1 00:00:01 2001', {stubbed}
  956.               ' by Joe Isuzu');         {put your name here!}
  957.  
  958.     END;  {of Write_Header}
  959.  
  960.  
  961.   BEGIN  {Shar}
  962.  
  963.     Find_All;                             {load dynamic array of wildcard
  964.                                            filenames}
  965.     Show_TargetNames;                     {v1.1}
  966.  
  967.     Write_Header;                         {output the shar header}
  968.  
  969.     curr := head;                         {start with first filename}
  970.     WHILE curr <> NIL DO BEGIN
  971.  
  972.       FName := curr^.Dir + curr^.SrchRec.Name;  {full filename}
  973.       Write_StdErr('shar: adding ' + FName + CRLF);  {v1.1}
  974.  
  975.       Assign(InFile, FName);
  976.       Reset(InFile);                      {open input file}
  977.       Writeln('cat << \SHAR_EOF > ' + curr^.SrchRec.Name); {'test1.doc'}
  978.       WHILE NOT Eof(InFile) DO BEGIN
  979.         {$I-}  Readln(InFile,S);
  980.         IF IoResult <> 0 THEN BEGIN
  981.           Write_StdErr('Read error: ' + Bracketed(FName) + CRLF);  {v1.1}
  982.           Close(InFile);
  983.         {$I+}
  984.           IF IoResult <> 0 THEN;           {we don't care}
  985.           Exit;                            {die}
  986.         END;
  987.  
  988.         Writeln(S);                        {let Turbo and DOS worry
  989.                                             about output errors}
  990.       END;
  991.  
  992.       Writeln('SHAR_EOF');
  993.       {$I-}  Close(InFile);  {$I+}        {close input file}
  994.       IF IoResult <> 0 THEN ;             {we don't care}
  995.  
  996.       curr := curr^.flink;                {next file ptr}
  997.     END;  {wend}
  998.  
  999.     Writeln('#    End of shell archive');   {neat ending}
  1000.     Writeln('exit 0');                    {even neater}
  1001.   END;  {of Shar}
  1002.  
  1003.  
  1004. BEGIN  {Main}
  1005.   Get_Args;                             {process cmdline args (may die)}
  1006.  
  1007.   IF Args[1] = '-U'                     {He wants us to unshar something...}
  1008.   THEN UnShar                           {...so do it}
  1009.   Else Shar;                            {ok, shar everything}
  1010.  
  1011. END.
  1012.