-- RStatsSupportImpl.Mesa, last edit January 3, 1983 5:34 pm
-- Pilot 6.0/ Mesa 7.0
--
-- to compute statistics
--
-- RStats -i Cedar3.1.DF
--
-- then
--   RStats -b		for the bcds
--   RStats -d		for the DF files
--   RStats -s		for the source files
--   RStats -t			for totals on # and size of files
--   RStats -x		produce DF cross reference on XRef.DFXRef

 DIRECTORY
  CS: TYPE USING[Flat],
  CWF: TYPE USING[SWF2, SWF3],
  Date: TYPE USING[StringToPacked],
  DFSubr: TYPE USING[DFSeq],
  DFUser: TYPE USING[CleanupEnumerate, EnumerateEntries, EnumerateProcType],
  Environment: TYPE USING[Comparison],
  FileIO: TYPE USING[Open],
  FQ: TYPE USING[FileQuery, Result],
  IFSFile: TYPE USING[Close, FileHandle, GetTimes, UnableToLogin],
  IO: TYPE USING[card, Close, Handle, PutChar, PutF, rope, string],
  LeafSubr: TYPE USING[Open],
  List: TYPE USING[CompareProc],
  RStatsSupport: TYPE USING[BcdDep, DFDep, DFRec, XRef],
  Rope: TYPE USING[Compare, Find, Flatten, ROPE, Text],
  Stream: TYPE USING[EndOfStream, GetChar, Handle],
  Subr: TYPE USING [AbortMyself, FileError, strcpy, TTYProcs],
  TypeScript: TYPE USING[TS, UserAbort];

RStatsSupportImpl: PROGRAM
IMPORTS CS, CWF, Date, DFUser, FileIO, FQ, IFSFile, IO, LeafSubr, Rope, 
	Stream, Subr, TypeScript 
EXPORTS RStatsSupport = {

LeafOpenWithCreate: PUBLIC PROC[host, directory, shortname: LONG STRING,
	version: CARDINAL, createtime: LONG CARDINAL, out: IO.Handle,
	tty: Subr.TTYProcs] RETURNS[fh: IFSFile.FileHandle] =
	{
	sfn: STRING ← [100];
			
	IF version > 0 -- AND criterion = none -- AND createtime = 0 THEN
		CWF.SWF3[sfn, "<%s>%s!%u"L, directory, shortname, @version]
	ELSE 
		CWF.SWF2[sfn, "<%s>%s"L, directory, shortname];	-- this gets !H
	fh	← LeafSubr.Open[host, sfn, tty, oldReadOnly
		! IFSFile.UnableToLogin => 
			  	IF reason = io THEN {
					out.PutF["Unable to analyze - no Leaf server on %s.\n", 
						IO.string[host]];
					GOTO err;
					};
			];
	IF createtime ~= 0 AND IFSFile.GetTimes[fh].create ~= createtime THEN {
		fres: FQ.Result;
		remoteVersion: CARDINAL;
		targetFileName: STRING ← [125];
		IFSFile.Close[fh];
		[fres: fres, remoteVersion: remoteVersion] ← FQ.FileQuery[host, directory, 
				shortname, version, createtime, FALSE, tty, targetFileName];
		SELECT fres FROM 
		foundCorrectVersion => {
			CWF.SWF3[sfn, "<%s>%s!%u"L, directory, shortname, @remoteVersion];
			fh ← LeafSubr.Open[host, sfn, tty, oldReadOnly];
			};
		foundWrongVersion => 
			ERROR Subr.FileError[wrongVersion];
		notFound =>
			ERROR Subr.FileError[notFound];
		ENDCASE => ERROR;
		};
	EXITS
	err => RETURN[NIL];
	};

BreakUp: PUBLIC PROC[line: Rope.ROPE] RETURNS[filename: Rope.Text, create: LONG CARDINAL] =
	{
	pos: INT ← Rope.Find[line, " "];
	IF pos < 0 THEN {
		filename ← CS.Flat[line];
		create ← 0;
		}
	ELSE {
		sd: Rope.Text;
		short: STRING ← [100];
		filename ← Rope.Flatten[line, 0, pos];
		sd ← Rope.Flatten[line, pos+1];
		Subr.strcpy[short, LOOPHOLE[sd]];
		create ← Date.StringToPacked[short];
		};
	};
	
CompareDFRec: PUBLIC List.CompareProc = CHECKED {
	da, db: REF RStatsSupport.DFRec;
	compare: Environment.Comparison;
	da ← NARROW[ref1];
	db ← NARROW[ref2];
	-- assumes Rope.Compare and List.Comparison use same return values
	compare ← Rope.Compare[da.shortname, db.shortname, FALSE];
	IF compare = equal THEN 
		compare ← Rope.Compare[da.directory, db.directory, FALSE];
	RETURN[compare];
	};
		
CompareDFDep: PUBLIC List.CompareProc = CHECKED {
	da, db: REF RStatsSupport.DFDep;
	compare: Environment.Comparison;
	da ← NARROW[ref1];
	db ← NARROW[ref2];
	-- assumes Rope.Compare and List.Comparison use same return values
	compare ← Rope.Compare[da.shortname, db.shortname, FALSE];
	IF compare = equal THEN {
		IF da.createtime ~= 0 THEN 
			compare ← IF da.createtime < db.createtime THEN less
				ELSE IF da.createtime = db.createtime THEN equal
				ELSE greater
		ELSE compare ← Rope.Compare[da.directory, db.directory, FALSE];
		};
	RETURN[compare];
	};
		
CompareBcdDep: PUBLIC List.CompareProc = CHECKED {
	da, db: REF RStatsSupport.BcdDep;
	compare: Environment.Comparison;
	da ← NARROW[ref1];
	db ← NARROW[ref2];
	-- assumes Rope.Compare and List.Comparison use same return values
	compare ← CompareDFRec[da.dfrec, db.dfrec];
	IF compare = equal THEN 
		compare ← Rope.Compare[da.bcdName, db.bcdName, FALSE];
	RETURN[compare];
	};

CompareXRef: PUBLIC List.CompareProc = CHECKED {
	da, db: REF RStatsSupport.XRef;
	compare: Environment.Comparison;
	da ← NARROW[ref1];
	db ← NARROW[ref2];
	-- assumes Rope.Compare and List.Comparison use same return values
	compare ← Rope.Compare[da.shortname, db.shortname, FALSE];
	IF compare = equal THEN 
		compare ← Rope.Compare[da.dfshortname, db.dfshortname, FALSE];
	RETURN[compare];
	};

-- puts \n at end of line
GetLine: PUBLIC PROC[sh: Stream.Handle, line: LONG STRING, out: IO.Handle]
	RETURNS[noteof: BOOL] = {
	i: CARDINAL ← 0;
	ch: CHAR;
	DO
		IF i >= line.maxlength THEN EXIT;
		ch ← Stream.GetChar[sh
			! Stream.EndOfStream => EXIT
			];
		line[i] ← ch;
		IF line[i] = '\000 AND i > 0 AND line[i-1] = '\000 THEN {
			-- tioga formatting, ignore rest of document
			i ← i - 1;
			DO 
				[] ← Stream.GetChar[sh
					! Stream.EndOfStream => EXIT	-- from inner loop
					];
				ENDLOOP;
			EXIT;	-- from outer loop
			};
		i ← i + 1;
		IF ch = '\n THEN EXIT;
		ENDLOOP;
	line.length ← i;
	IF line.length >= line.maxlength THEN {
		line.length ← line.maxlength;
		out.PutF["GetLine-- line '%s' is too long.\n", IO.string[line]];
		};
	RETURN[line.length ~= 0];
	};

-- code to read in DF files and produce a list of files
ComputeFileList: PUBLIC PROC[dfFileName: LONG STRING, fileListName: Rope.Text,
	typeScript: TypeScript.TS, out: IO.Handle, tty: Subr.TTYProcs] =
	{
	file: IO.Handle;
	
	OneFile: DFUser.EnumerateProcType = 
		{
		file.PutF["[%s]<%s>%s", IO.string[host], IO.string[directory], IO.string[shortName]];
		IF version > 0 THEN file.PutF["!%s", IO.card[version]];
		IF createTime > 0 THEN file.PutF[" %t", IO.card[createTime]];
		file.PutChar['\n];
		IF TypeScript.UserAbort[typeScript] THEN SIGNAL Subr.AbortMyself;
		};
	
	file ← FileIO.Open[fileListName, overwrite];
	DFUser.EnumerateEntries[dfFileName: dfFileName, procToCall: OneFile,
		nEntriesInDFFiles: 8000, confirmBeforeOverwriting: FALSE,
		printProgressMessages: TRUE, sortOption: byLongName,
		tryDollars: FALSE, in: tty.in, out: tty.out, confirmData: tty.data, Confirm: tty.Confirm
		! UNWIND => DFUser.CleanupEnumerate[]];
	file.Close[];
	out.PutF["List of files written on %s\n", IO.rope[fileListName]];
	DFUser.CleanupEnumerate[];
	};
	
-- must be delimited by non-alphabetic chars
SubString: PUBLIC PROC[line, match: LONG STRING] RETURNS[is: BOOL] = 
	{
	is ← FALSE;
	FOR i: CARDINAL IN [0 .. line.length) DO
		IF line[i] = match[0] AND i + match.length <= line.length THEN
			FOR j: CARDINAL IN [0 .. match.length) DO
				IF line[i+j] ~= match[j] THEN EXIT;
				REPEAT
				FINISHED => {
					f, l: CARDINAL;
					f ← i-1;	-- char before
					l ← i+match.length;	-- char after
					IF  (i = 0 OR (line[f]  NOT IN ['A .. 'Z] AND line[f] NOT IN ['a .. 'z]))
					AND
						(l >= line.length OR (line[l] NOT IN ['A .. 'Z] AND line[l] NOT IN ['a .. 'z]))
					THEN
						RETURN[TRUE];
					};
				ENDLOOP;
		ENDLOOP;
	};
	

}.