-- VersionPack.Mesa  Edited by Sandman on July 8, 1980  9:02 AM  
-- Copyright  Xerox Corporation 1979, 1980

DIRECTORY
  AltoDefs USING [BytesPerWord, PageSize],
  BcdDefs USING [BCD, FTIndex, FTRecord, VersionID, VersionStamp, Base],
  BcdOps USING [NameString],
  FastDirDefs USING [
    LookupItem, ScanDir, AllFileSwitches, Cap, StripExtension, FileType,
    GetExtension, ItemHandle],
  ImageDefs USING [StopMesa],
  ImageFormat USING [VersionID],
  IODefs USING [
    CR, ReadChar, ReadID, SP, WriteChar, WriteDecimal, WriteLine, WriteOctal,
    WriteString],
  SegmentDefs USING [
    DeleteFileSegment, FileSegmentAddress, FileSegmentHandle, InsertFile,
    MoveFileSegment, NewFileSegment, Read, SwapIn, Unlock],
  StreamDefs USING [
    CreateByteStream, NewByteStream, Read, ReadBlock, StreamHandle],
  StringDefs USING [
    AppendChar, AppendSubString, EquivalentString, StringToDecimal,
    SubStringDescriptor],
  Storage USING [Pages, FreePages],
  TimeDefs USING [
    AppendDayTime, DefaultTime, InvalidTime, PackDT, PackedTime, UnpackDT,
    UnpackedTime];

VersionPack: PROGRAM
  IMPORTS
    FastDirDefs, ImageDefs, IODefs, SegmentDefs, StreamDefs, StringDefs, Storage,
    TimeDefs =
  BEGIN OPEN IODefs, StreamDefs, StringDefs;
  
  BcdBase: TYPE = POINTER TO BcdDefs.BCD;
  UnpackedTime: TYPE = TimeDefs.UnpackedTime;
  
  BinaryVersion: VersionProc =
    BEGIN OPEN SegmentDefs;
    bcd: BcdBase;
    seg: FileSegmentHandle ← NewFileSegment[
      InsertFile[@info.fp, Read], 1, 1, Read];
    SwapIn[seg];
    bcd ← FileSegmentAddress[seg];
    SELECT bcd.versionIdent FROM
      BcdDefs.VersionID =>
	BEGIN
	PrintVersion[bcd.version];
	WriteChar[CR];
	IF type = bcd AND verbose THEN
	  BEGIN
	  IF bcd.nPages > 1 THEN
	    BEGIN
	    Unlock[seg];
	    MoveFileSegment[seg, 1, bcd.nPages];
	    SwapIn[seg];
	    bcd ← FileSegmentAddress[seg];
	    END;
	  PrintFileVersions[bcd];
	  END;
	END;
      ImageFormat.VersionID =>
	BEGIN PrintVersion[bcd.version]; WriteChar[CR]; END;
      ENDCASE =>
	BEGIN
	WriteString[" unknown version ID "L];
	WriteDecimal[bcd.versionIdent];
	WriteChar[CR];
	END;
    Unlock[seg];
    DeleteFileSegment[seg];
    RETURN
    END;
    
  BufferObject: TYPE = RECORD [p: CARDINAL, s: STRING];
  Buffer: TYPE = POINTER TO BufferObject;
  
  SourceDate: VersionProc =
    BEGIN OPEN AltoDefs;
    LI: TYPE = LONG INTEGER;
    date, tdate: TimeDefs.PackedTime;
    b: BufferObject;
    stream: StreamHandle;
    found: BOOLEAN ← FALSE;
    stream ← CreateByteStream[SegmentDefs.InsertFile[@info.fp, Read], Read];
    b.s ← Storage.Pages[1];
    b.s↑ ←
      [length: 0, maxlength: (PageSize - SIZE[StringBody])*BytesPerWord, text:];
    b.s.length ←
      ReadBlock[stream, @b.s.text, b.s.maxlength/BytesPerWord]*BytesPerWord;
    b.p ← 0;
    [found, date] ← GetDate[@b ! TimeDefs.InvalidTime => CONTINUE];
    IF found THEN
      BEGIN
      DO
	[found, tdate] ← GetDate[@b ! TimeDefs.InvalidTime => EXIT];
	IF ~found THEN EXIT;
	IF LOOPHOLE[tdate, LI] > LOOPHOLE[date, LI] THEN date ← tdate;
	ENDLOOP;
      WriteChar[' ];
      PrintDate[TimeDefs.UnpackDT[date]];
      END
    ELSE WriteString[" ?"L];
    WriteChar[CR];
    Storage.FreePages[b.s];
    stream.destroy[stream];
    RETURN
    END;
    
  GetDate: PROCEDURE [b: Buffer] RETURNS [BOOLEAN, TimeDefs.PackedTime] =
    BEGIN
    token: STRING ← [20];
    found: BOOLEAN ← FALSE;
    index: CARDINAL [0..12);
    u: UnpackedTime ← [0, 0, 0, 0, 0, 0, 0, 0, FALSE];
    NextItem[b, token];
    WHILE token.length # 0 DO
      [found, index] ← IsMonth[token];
      IF found THEN
	BEGIN
	NextItem[b, token];
	IF IsNumber[token] THEN
	  BEGIN u.month ← index; u.day ← StringToDecimal[token]; END
	ELSE LOOP;
	END
      ELSE
	IF IsNumber[token] THEN -- possible Day-Mo-Yr form
	  BEGIN
	  u.day ← StringToDecimal[token];
	  NextItem[b, token];
	  [found, index] ← IsMonth[token];
	  IF found THEN u.month ← index ELSE LOOP;
	  END;
      IF found THEN
	BEGIN
	NextItem[b, token];
	IF IsNumber[token] THEN
	  BEGIN
	  u.year ← StringToDecimal[token];
	  IF u.year < 100 THEN u.year ← u.year + 1900;
	  END
	ELSE LOOP;
	NextItem[b, token];
	IF IsNumber[token] THEN
	  BEGIN
	  u.hour ← StringToDecimal[token];
	  NextItem[b, token];
	  IF IsNumber[token] THEN u.minute ← StringToDecimal[token];
	  NextItem[b, token];
	  IF EquivalentString[token, "PM"L] AND u.hour < 12 THEN
	    u.hour ← u.hour + 12;
	  END;
	RETURN[TRUE, TimeDefs.PackDT[u, TRUE]]
	END;
      NextItem[b, token];
      ENDLOOP;
    RETURN[FALSE, TimeDefs.DefaultTime]
    END;
    
  IsMonth: PROCEDURE [candidate: STRING] RETURNS [BOOLEAN, CARDINAL] =
    BEGIN
    Months: ARRAY [0..12) OF STRING =
      ["JANUARY"L, "FEBRUARY"L, "MARCH"L, "APRIL"L, "MAY"L, "JUNE"L, "JULY"L,
	"AUGUST"L, "SEPTEMBER"L, "OCTOBER"L, "NOVEMBER"L, "DECEMBER"L];
    i, j: CARDINAL;
    test: STRING;
    IF candidate.length >= 3 THEN
      FOR i IN [0..12) DO
	test ← Months[i];
	IF candidate.length <= test.length THEN
	  FOR j IN [0..candidate.length) DO
	    IF FastDirDefs.Cap[candidate[j]] # test[j] THEN EXIT;
	    REPEAT FINISHED => RETURN[TRUE, i];
	    ENDLOOP;
	ENDLOOP;
    RETURN[FALSE, 0]
    END;
    
  IsNumber: PROCEDURE [s: STRING] RETURNS [BOOLEAN] =
    BEGIN
    i: CARDINAL;
    FOR i IN [0..s.length) DO IF s[i] ~ IN ['0..'9] THEN RETURN[FALSE]; ENDLOOP;
    RETURN[TRUE]
    END;
    
  NextItem: PROCEDURE [b: Buffer, token: STRING] =
    BEGIN
    c: CHARACTER;
    token.length ← 0;
    WHILE b.p < b.s.length DO
      c ← b.s[b.p];
      b.p ← b.p + 1;
      SELECT c FROM
	IN ['a..'z], IN ['A..'Z], IN ['0..'9] =>
	  IF token.length < token.maxlength THEN AppendChar[token, c];
	ENDCASE => IF token.length # 0 THEN EXIT;
      ENDLOOP;
    RETURN
    END;
    
  PrintFileVersions: PROCEDURE [bcd: BcdBase] =
    BEGIN OPEN BcdDefs;
    line: STRING ← [40];
    filename: SubStringDescriptor;
    fti: FTIndex;
    ftb: Base = LOOPHOLE[bcd + bcd.ftOffset];
    stb: BcdOps.NameString = LOOPHOLE[bcd + bcd.ssOffset];
    FOR fti ← FIRST[FTIndex], fti + SIZE[FTRecord] UNTIL fti = bcd.ftLimit DO 
      OPEN f: @ftb[fti];
      filename ← SubStringDescriptor[@stb.string, f.name, stb.size[f.name]];
      line.length ← 0;
      WriteChar[' ];
      PrintVersion[f.version];
      WriteChar[' ];
      AppendSubString[line, @filename];
      WriteLine[line];
      ENDLOOP;
    RETURN
    END;
    
  PrintVersion: PROCEDURE [stamp: BcdDefs.VersionStamp] =
    BEGIN
    WriteChar[' ];
    PrintDate[TimeDefs.UnpackDT[stamp.time]];
    WriteChar[' ];
    WriteOctal[stamp.net];
    WriteChar['#];
    WriteOctal[stamp.host];
    WriteChar['#];
    END;
    
  PrintDate: PROCEDURE [dt: UnpackedTime] =
    BEGIN
    tmp: STRING ← [40];
    TimeDefs.AppendDayTime[tmp, dt];
    WriteString[tmp];
    END;
    
  -- command line stuff
  
  
  NextFile: PROCEDURE [token: STRING] RETURNS [BOOLEAN];
  
  ReadFromCmdFile: PROCEDURE [token: STRING] RETURNS [BOOLEAN] =
    BEGIN RETURN[ReadCmdStream[cmdstream, token]] END;
    
  ReadFromKeyboard: PROCEDURE [token: STRING] RETURNS [BOOLEAN] =
    BEGIN
    WriteString["File: "L];
    ReadID[token];
    WriteChar[CR];
    RETURN[token.length # 0];
    END;
    
  ReadCmdStream: PROCEDURE [stream: StreamHandle, token: STRING]
    RETURNS [BOOLEAN] =
    BEGIN
    c: CHARACTER;
    token.length ← 0;
    WHILE ~stream.endof[stream] DO
      c ← stream.get[stream];
      SELECT c FROM
	SP => IF token.length # 0 THEN EXIT;
	CR => EXIT;
	ENDCASE => AppendChar[token, c];
      ENDLOOP;
    RETURN[token.length # 0];
    END;
    
  cmdstream: StreamHandle;
  
  SetCommandInput: PROCEDURE =
    BEGIN
    cmdstream ← NewByteStream["Com.Cm"L, Read];
    [] ← ReadCmdStream[cmdstream, name];
    StripSwitches[name]; -- mesa.image
    [] ← ReadCmdStream[cmdstream, name];
    IF interactive ← cmdstream.endof[cmdstream] THEN
      BEGIN cmdstream.destroy[cmdstream]; NextFile ← ReadFromKeyboard; END
    ELSE NextFile ← ReadFromCmdFile;
    RETURN
    END;
    
  StripSwitches: PROCEDURE [token: STRING] =
    BEGIN
    i, j: CARDINAL;
    option: BOOLEAN ← TRUE;
    FOR i IN [0..token.length) DO
      IF token[i] = '/ THEN
	BEGIN
	FOR j IN (i..token.length) DO
	  SELECT token[
	    j] FROM
	    '~, '- => option ← ~option;
	    'p, 'P => BEGIN pause ← option; option ← TRUE END;
	    'v, 'V => BEGIN verbose ← option; option ← TRUE END;
	    ENDCASE;
	  ENDLOOP;
	token.length ← i;
	END;
      ENDLOOP;
    RETURN
    END;
    
  -- main program
  
  VersionProc: TYPE = PROCEDURE [
    info: FastDirDefs.ItemHandle, type: FastDirDefs.FileType];
  
  dateproc: ARRAY FastDirDefs.FileType OF VersionProc =
    [SourceDate, BinaryVersion, SourceDate, BinaryVersion, BinaryVersion,
      BinaryVersion];
  
  i: FastDirDefs.FileType;
  name: STRING ← [100];
  
  interactive: BOOLEAN;
  pause, pauseDflt: BOOLEAN ← FALSE;
  verbose, verboseDflt: BOOLEAN ← FALSE;
  item: FastDirDefs.ItemHandle;
  
  SetCommandInput[];
  FastDirDefs.ScanDir[FastDirDefs.AllFileSwitches, ];
  pauseDflt ← pause;
  verboseDflt ← verbose;
  WriteChar[CR];
  WHILE NextFile[name] DO
    pause ← pauseDflt;
    verbose ← verboseDflt;
    StripSwitches[name];
    IF name.length = 0 THEN
      BEGIN
      pauseDflt ← pause;
      verboseDflt ← verbose;
      IF interactive THEN WriteChar[CR];
      END
    ELSE
      BEGIN
      FastDirDefs.StripExtension[name, NIL];
      IF ~interactive THEN WriteLine[name];
      FOR i IN FastDirDefs.FileType DO
	IF (item ← FastDirDefs.LookupItem[name, i]) # NIL THEN
	  BEGIN
	  WriteChar[' ];
	  WriteString[FastDirDefs.GetExtension[i]];
	  WriteChar[':];
	  dateproc[i][item, i];
	  END;
	ENDLOOP;
      WriteChar[CR];
      IF pause THEN [] ← ReadChar[];
      END;
    ENDLOOP;
  ImageDefs.StopMesa[];
  
  END.