TextDocs.NewDocCWindowsLeft4WindowsRightfWindowsTopWindowsButtomColorFlatLockedControlsOrgBIER3Oberon10.Scn.FntSyntax10.Scn.FntF;i1Syntax10b.Scn.FntSyntax10m.Scn.Fntd]\VyCU&W#թ(* ETH Oberon, Copyright 2001 ETH Zuerich Institut fuer Computersysteme, ETH Zentrum, CH-8092 Zuerich. Refer to the "General ETH Oberon System Source License" contract available at: http://www.oberon.ethz.ch/ *) MODULE Packages; (** portable / source: Win32.Packages.Mod *) (* ejz *) IMPORT Kernel32, Kernel:=AosHeap, AosConfiguration, FileDir, Files, Objects, Dates, Strings, Display, Texts, Oberon, Compress, OPM, OPS, Compiler, Gadgets, Documents, Watson, Attributes, Links,Win32FS; (** 030528 fof: removed bug in Install -> "." replaced by "\" for registry entries Module Packages is used to build (see: Packages.Build) and install (see: Packages.Install) application packages. Application packages are compressed files containing application files together with installation instructions and version information. Package file format: DocHead Compress ( PackHead { Data | URL | Cmd } ) . DocHead = Documents.Id "Packages.NewDoc" (INTEGER) 0 0 0 0 Attachments . PackHead = HeadTag Resource gen options . Data = DataTag Resource len data . URL = URLTag Resource url options . Cmd = CmdTag Resource parstring1 parstring2 . Resource = name len time date minor major . *) CONST HeadTag = 0; DataTag = 1; URLTag = 2; CmdTag = 3; VerNewResource = 0; VerSameVersion = 1; VerNewBuild = 2; VerOldBuild = 3; VerNewMajorVersion = 4; VerOldMajorVersion = 5; VerNewMinorVersion = 6; VerOldMinorVersion = 7; VerError = 8; NoPaths = 7; TYPE ResourcePtr = POINTER TO Resource; Resource = RECORD name: FileDir.FileName; time, date, pos: LONGINT; minor, major: INTEGER; next: ResourcePtr END; Package = RECORD (Resource) R: Files.Rider; gen, options: FileDir.FileName END; (** 0: Done, 1: NetworkErr, 2: UserBreak, 3: OtherError, -1: Transfering (see also PlugIns.Mod) *) GetURLProc* = PROCEDURE (context: Objects.Object; url: ARRAY OF CHAR): LONGINT; VAR W: Texts.Writer; regRoot: FileDir.FileName; paths: ARRAY NoPaths OF RECORD name: Objects.Name; path: FileDir.FileName END; findDefName: Compress.Name; findDefD, findDefT, errors: LONGINT; useCrypt, noReg, userPath: BOOLEAN; PROCEDURE InitPaths(user: ARRAY OF CHAR); VAR i, j: LONGINT; done: BOOLEAN; BEGIN IF user = "" THEN userPath := FALSE; paths[0].name := "SYSTEM"; paths[1].name := "OBJ"; paths[2].name := "SRC"; paths[3].name := "APPS"; paths[4].name := "DOCU"; paths[5].name := "WORK"; paths[6].name := "OBERON"; (* Registry.OberonPath("Packages\Directories", regRoot); *) regRoot := "Packages.Directories"; FOR i := 0 TO NoPaths-1 DO IF ~AosConfiguration.GetKeyValue(Oberon.configuration,regRoot,paths[i].name,paths[i].path) THEN COPY("",paths[i].path) END; (* Registry.GetKeyValue(Registry.CurrentUser, regRoot, paths[i].name, paths[i].path); IF Registry.res # Registry.Done THEN COPY("", paths[i].path) END *) END; Win32FS.EnumerateSearchPaths(PathEnumerator); FOR i := 0 TO NoPaths-1 DO IF paths[i].path = "" THEN Win32FS.GetWorkingDirectory(paths[i].path) END END; Kernel32.GetModuleFileName(Kernel.hInstance, paths[6].path, LEN(paths[6].path)); Win32FS.ConvertChar(paths[6].path, "\", FileDir.PathChar); i := 0; j := -1; WHILE paths[6].path[i] # 0X DO IF paths[6].path[i] = FileDir.PathChar THEN j := i END; INC(i) END; paths[6].path[j] := 0X ELSE userPath := TRUE; Win32FS.GetWorkingDirectory(paths[1].path); Win32FS.ChangeDirectory(user, done); ASSERT(done); Win32FS.GetWorkingDirectory(paths[0].path); Win32FS.ChangeDirectory(paths[1].path, done); ASSERT(done); FOR i := 1 TO NoPaths-1 DO COPY(paths[0].path, paths[i].path) END END; (* Registry.OberonPath("Packages", regRoot) *) regRoot := "Packages"; END InitPaths; PROCEDURE Init*(user: ARRAY OF CHAR); BEGIN IF userPath OR (user # "") THEN InitPaths(user) END; useCrypt := FALSE; noReg := FALSE END Init; PROCEDURE OpenScanner(VAR T: Texts.Text; VAR S: Texts.Scanner); VAR crypt, key: ARRAY 64 OF CHAR; beg, end, time: LONGINT; BEGIN Init(""); Texts.OpenScanner(S, Oberon.Par.text, Oberon.Par.pos); Texts.Scan(S); WHILE (S.class = Texts.Char) & (S.c = Oberon.OptionChar) DO Texts.Scan(S); IF (S.class = Texts.Name) & (CAP(S.s[0]) = "C") THEN (* encryption *) Texts.Scan(S); ASSERT(S.class = Texts.Name); COPY(S.s, crypt); Texts.Scan(S); ASSERT(S.class IN {Texts.Name, Texts.String}); COPY(S.s, key); Compress.InstallCrypt(crypt, key); Texts.Scan(S); useCrypt := TRUE ELSIF (S.class = Texts.Name) & (CAP(S.s[0]) = "R") THEN (* registry *) Texts.Scan(S); noReg := (S.class = Texts.Char) & (S.c = "-"); Texts.Scan(S) ELSIF (S.class = Texts.Name) & (CAP(S.s[0]) = "U") THEN (* user directory *) Texts.Scan(S); ASSERT(S.class IN {Texts.Name, Texts.String}); InitPaths(S.s); Texts.Scan(S) END END; IF S.class = Texts.Char THEN T := NIL; beg := 0; IF S.c = "^" THEN Oberon.GetSelection(T, beg, end, time) ELSIF S.c = "*" THEN T := Oberon.MarkedText() END; IF T # NIL THEN Texts.OpenScanner(S, T, beg); Texts.Scan(S) ELSE S.class := Texts.Inval; S.eot := TRUE END ELSE T := Oberon.Par.text END END OpenScanner; PROCEDURE WriteResource(VAR R: Files.Rider; VAR res: Resource; tag: INTEGER; dLen: LONGINT); BEGIN Files.WriteInt(R, tag); Files.WriteString(R, res.name); Files.WriteLInt(R, dLen+12); Files.WriteLInt(R, res.time); Files.WriteLInt(R, res.date); Files.WriteInt(R, res.minor); Files.WriteInt(R, res.major) END WriteResource; PROCEDURE ClosePackage(VAR pack: Package); VAR F: Files.File; R: Files.Rider; h: Compress.Header; attr: Attributes.BoolAttr; BEGIN F := Files.Base(pack.R); h.length := Files.Length(F); Files.Set(pack.R, F, 0); F := Files.New(pack.name); Files.Set(R, F, 0); (* Document Head *) Files.WriteInt(R, Documents.Id); Files.WriteString(R, "Packages.NewDoc"); Files.WriteInt(R, 0); Files.WriteInt(R, 0); Files.WriteInt(R, 0); Files.WriteInt(R, 0); h.extensions := {}; IF useCrypt THEN INCL(h.extensions, Compress.encryption); NEW(attr); attr.next := NIL; attr.name := "Encryption"; attr.b := TRUE ELSE attr := NIL END; Documents.StoreAttachments(R, attr, NIL); Compress.CopyToArc(pack.R, R, h.length, h); Files.Register(F); Files.Set(pack.R, NIL, 0) END ClosePackage; (** Builds new application package(s) from a package description. Syntax: packages = [ "\C" crypt key ] package { ";" package } "~" . package = "PACKAGE" name version gen [ "\" options ] { data | url | cmd } . options: "A" data = "DATA" name [ version ] [ ":=" file ] . url = "URL" name [ version ] [ "\" options ] [ ":=" url ] . options: "S" cmd = filecmd | "DEST" dir | "NEWDEST" dir | "MSG" text | "SET" key ":=" value . filecmd = ( "COMPILE" | "COPY" | "DEF" ) name . *) PROCEDURE Build*; VAR T: Texts.Text; S: Texts.Scanner; pos: LONGINT; pack: Package; datafix: ResourcePtr; err: BOOLEAN; PROCEDURE Error(msg1, msg2: ARRAY OF CHAR); VAR col: SHORTINT; BEGIN IF ~err THEN col := W.col; Texts.SetColor(W, 1); Texts.WriteString(W, msg1); Texts.WriteString(W, msg2); Texts.WriteString(W, " at "); Texts.WriteInt(W, Texts.Pos(S), 0); Texts.WriteLn(W); Texts.SetColor(W, col) END; err := TRUE END Error; PROCEDURE Warning(msg1, msg2: ARRAY OF CHAR); VAR col: SHORTINT; BEGIN IF ~err THEN col := W.col; Texts.SetColor(W, 3); Texts.WriteString(W, msg1); Texts.WriteString(W, msg2); Texts.WriteString(W, " at "); Texts.WriteInt(W, Texts.Pos(S), 0); Texts.WriteLn(W); Texts.SetColor(W, col) END; END Warning; PROCEDURE UseData(name: ARRAY OF CHAR); VAR res: ResourcePtr; BEGIN res := datafix; WHILE (res # NIL) & (res.name # name) DO res := res.next END; IF res = NIL THEN NEW(res); res.next := datafix; datafix := res; COPY(name, res.name); res.pos := -1; res.minor := pack.minor; res.major := pack.major END END UseData; PROCEDURE AddData(name: ARRAY OF CHAR); VAR res: ResourcePtr; BEGIN res := datafix; WHILE (res # NIL) & (res.name # name) DO res := res.next END; IF res = NIL THEN NEW(res); res.next := datafix; datafix := res; COPY(name, res.name) END; res.pos := Files.Pos(pack.R) END AddData; PROCEDURE Scan(); BEGIN pos := Texts.Pos(S); Texts.Scan(S) END Scan; PROCEDURE Version(VAR res: Resource); BEGIN IF S.class = Texts.Int THEN res.major := SHORT(S.i); res.minor := 0 ELSIF S.class = Texts.Real THEN res.major := SHORT(ENTIER(S.x)); res.minor := SHORT(ENTIER(100.0*(S.x-res.major)+0.5)) ELSE Error("version expected", "") END; Scan() END Version; PROCEDURE Name(VAR name: ARRAY OF CHAR); BEGIN IF S.class = Texts.Name THEN COPY(S.s, name); Scan() ELSE Error("name expected", "") END END Name; PROCEDURE String(VAR str: ARRAY OF CHAR); VAR R: Texts.Reader; i: LONGINT; BEGIN IF S.class IN {Texts.Name, Texts.String} THEN COPY(S.s, str); Scan() ELSIF (S.class = Texts.Char) & (S.c = "'") THEN Texts.OpenReader(R, T, pos+1); i := 0; Texts.Read(R, str[i]); WHILE str[i] # "'" DO INC(i); Texts.Read(R, str[i]) END; str[i] := 0X; Texts.OpenScanner(S, T, Texts.Pos(R)); Scan() ELSE Error("string expected", "") END END String; PROCEDURE Equals(); BEGIN IF (S.class = Texts.Char) & (S.c = ":") THEN Scan(); IF (S.class = Texts.Char) & (S.c = "=") THEN Scan(); RETURN END END; Error(":= expected", "") END Equals; PROCEDURE ResHead(VAR res: Resource; VAR options, url: ARRAY OF CHAR); BEGIN Scan(); Name(res.name); IF S.class IN {Texts.Int, Texts.Real} THEN Version(res) ELSE res.minor := pack.minor; res.major := pack.major END; IF (S.class = Texts.Char) & (S.c = Oberon.OptionChar) THEN Scan(); Name(options) ELSE COPY("", options) END; IF (S.class = Texts.Char) & (S.c # "~") THEN Equals(); String(url) ELSE COPY(res.name, url) END END ResHead; PROCEDURE copyfile(file: ARRAY OF CHAR; VAR res: Resource); VAR F: Files.File; R: Files.Rider; buf: ARRAY 1024 OF CHAR; BEGIN F := Files.Old(file); IF F # NIL THEN Files.GetDate(F, res.time, res.date); WriteResource(pack.R, res, DataTag, 4+Files.Length(F)); Files.WriteLInt(pack.R, Files.Length(F)); Files.Set(R, F, 0); Files.ReadBytes(R, buf, 1024); WHILE ~R.eof DO Files.WriteBytes(pack.R, buf, 1024); Files.ReadBytes(R, buf, 1024) END; Files.WriteBytes(pack.R, buf, 1024-R.res) ELSE Error("file not found ", file) END END copyfile; PROCEDURE data(); VAR res: Resource; opt, file: FileDir.FileName; BEGIN ResHead(res, opt, file); copyfile(file, res); AddData(res.name) END data; PROCEDURE url(); VAR res: Resource; options, url: FileDir.FileName; BEGIN ResHead(res, options, url); res.time := Dates.ToTime(0, 0, 0); res.date := Dates.ToDate(1980, 1, 1); WriteResource(pack.R, res, URLTag, Strings.Length(url)+1+Strings.Length(options)+1); Files.WriteString(pack.R, url); Files.WriteString(pack.R, options) END url; PROCEDURE cmd(); VAR res: Resource; file, value: FileDir.FileName; BEGIN Name(res.name); value := ""; IF res.name = "COMPILE" THEN WHILE ~err & (S.class = Texts.Char) & (S.c = Oberon.OptionChar) DO Scan(); Name(file); Strings.Append(value, file) END; Name(file); UseData(file) ELSIF (res.name = "COPY") OR (res.name = "DEF") THEN Name(file); UseData(file) ELSIF (res.name = "DEST") OR (res.name = "NEWDEST") OR (res.name = "MSG") THEN String(file) ELSIF res.name = "SET" THEN String(file); Equals(); String(value) ELSE Error("unknown command ", res.name) END; WriteResource(pack.R, res, CmdTag, Strings.Length(file)+1+Strings.Length(value)+1); Files.WriteString(pack.R, file); Files.WriteString(pack.R, value) END cmd; PROCEDURE package(); BEGIN Scan(); datafix := NIL; Name(pack.name); Texts.WriteString(W, pack.name); Texts.WriteString(W," "); Version(pack); String(pack.gen); IF (S.class = Texts.Char) & (S.c = Oberon.OptionChar) THEN Scan(); Name(pack.options) ELSE pack.options := "" END; Files.Set(pack.R, Files.New(""), 0); Oberon.GetClock(pack.time, pack.date); WriteResource(pack.R, pack, HeadTag, Strings.Length(pack.gen)+1+Strings.Length(pack.options)+1); Files.WriteString(pack.R, pack.gen); Files.WriteString(pack.R, pack.options); LOOP IF ~err & (S.class = Texts.Name) THEN IF S.s = "DATA" THEN data() ELSIF S.s = "URL" THEN url() ELSIF S.s = "PACKAGE" THEN EXIT ELSE cmd() END ELSE IF ~((S.class = Texts.Char) & (S.c = "~")) THEN Error("unexpected end of input / ~ missing", ""); END; EXIT END END; IF ~err THEN WHILE ~err & (datafix # NIL) DO IF datafix.pos < 0 THEN Texts.WriteString(W, "adding DATA "); Texts.WriteString(W, datafix.name); Texts.WriteLn(W); copyfile(datafix.name, datafix^) END; datafix := datafix.next END; ClosePackage(pack); Texts.WriteString(W, " done"); Texts.WriteLn(W); Texts.Append(Oberon.Log, W.buf) END END package; BEGIN OpenScanner(T, S); err := FALSE; WHILE ~err & (S.class = Texts.Name) & (S.s = "PACKAGE") DO package() END; IF useCrypt THEN Compress.ClearCrypt(); useCrypt := FALSE END; Texts.Append(Oberon.Log, W.buf) END Build; PROCEDURE ReadResource(VAR R: Files.Rider; VAR res: Resource; VAR tag: INTEGER; VAR dLen: LONGINT); VAR p: LONGINT; BEGIN p := Files.Pos(R); Files.ReadInt(R, tag); Files.ReadString(R, res.name); Files.ReadLInt(R, dLen); dLen := dLen + Files.Pos(R)-p; Files.ReadLInt(R, res.time); Files.ReadLInt(R, res.date); Files.ReadInt(R, res.minor); Files.ReadInt(R, res.major) END ReadResource; PROCEDURE FindResource(VAR name: ARRAY OF CHAR; VAR res: Resource); VAR key, value: FileDir.FileName; x: LONGINT; i: INTEGER; BEGIN COPY(regRoot, key); Strings.AppendCh(key, "."); Strings.Append(key, name); IF AosConfiguration.GetKeyValue(Oberon.configuration,key,"Version",value) THEN (* Registry.GetKeyValue(Registry.CurrentUser, key, "Version", value); IF Registry.res = Registry.Done THEN *) i := 0; Strings.StrToIntPos(value, x, i); res.major := SHORT(x); INC(i); Strings.StrToIntPos(value, x, i); res.minor := SHORT(x); (* Registry.GetKeyValue(Registry.CurrentUser, key, "Date", value); *) IF AosConfiguration.GetKeyValue(Oberon.configuration,key,"Date",value) THEN END; Strings.StrToDate(value, res.date); IF AosConfiguration.GetKeyValue(Oberon.configuration,key,"Date",value) THEN END; (* Registry.GetKeyValue(Registry.CurrentUser, key, "Time", value); *) Strings.StrToTime(value, res.time); COPY(name, res.name) ELSE res.name := "" END END FindResource; PROCEDURE RegisterResource(VAR res: Resource; package: ARRAY OF CHAR); VAR key, value, str: FileDir.FileName; BEGIN IF noReg THEN RETURN END; COPY(regRoot, key); Strings.AppendCh(key, "."); Strings.Append(key, res.name); Strings.IntToStr(res.major, value); Strings.AppendCh(value, "."); Strings.IntToStr(res.minor, str); Strings.Append(value, str); IF AosConfiguration.SetKeyValue(Oberon.configuration,key,"Version", value) THEN END; (* Registry.SetKeyValue(Registry.CurrentUser, key, "Version", value); *) Strings.DateToStr(res.date, value); IF AosConfiguration.SetKeyValue(Oberon.configuration,key,"Date", value) THEN END; (* Registry.SetKeyValue(Registry.CurrentUser, key, "Date", value); *) Strings.TimeToStr(res.time, value); IF AosConfiguration.SetKeyValue(Oberon.configuration,key,"Time", value) THEN END; (* Registry.SetKeyValue(Registry.CurrentUser, key, "Time", value); *) IF (package # "") OR ~(res IS Package) THEN (* Registry.SetKeyValue(Registry.CurrentUser, key, "Package", package) *) IF AosConfiguration.SetKeyValue(Oberon.configuration,key,"Package", package) THEN END; END END RegisterResource; PROCEDURE CompareVersion(VAR old, new: Resource): LONGINT; BEGIN IF new.major > old.major THEN RETURN VerNewMajorVersion ELSIF new.major < old.major THEN RETURN VerOldMajorVersion ELSIF new.minor > old.minor THEN RETURN VerNewMinorVersion ELSIF new.minor < old.minor THEN RETURN VerOldMinorVersion ELSIF new.date > old.date THEN RETURN VerNewBuild ELSIF new.date = old.date THEN IF new.time > old.time THEN RETURN VerNewBuild ELSIF new.time < old.time THEN RETURN VerOldBuild ELSE RETURN VerSameVersion END ELSE RETURN VerOldBuild END END CompareVersion; PROCEDURE CheckVersion(VAR res: Resource; options: ARRAY OF CHAR; file: BOOLEAN): LONGINT; VAR ver: Resource; v: LONGINT; BEGIN IF noReg THEN RETURN VerNewResource END; FindResource(res.name, ver); IF ver.name = res.name THEN v := CompareVersion(ver, res); IF (v IN {VerSameVersion, VerOldBuild, VerOldMinorVersion}) & (options = "A") THEN RETURN VerNewBuild ELSE RETURN v END ELSIF file & ~(options = "A") & (Files.Old(res.name) # NIL) THEN RETURN VerError ELSE RETURN VerNewResource END END CheckVersion; PROCEDURE *FindDef(h: Compress.Header; VAR stop: BOOLEAN); BEGIN IF h.name = findDefName THEN findDefD := h.date; findDefT := h.time; stop := TRUE END END FindDef; PROCEDURE *CheckEntry(entry: AosConfiguration.tEntry (* name: ARRAY OF CHAR *) ); VAR F: Files.File; key, pkey, value: FileDir.FileName; ft, fd, rt, rd: LONGINT; res: INTEGER; error: BOOLEAN; name: ARRAY 256 OF CHAR; BEGIN IF (entry = NIL) OR (~(entry IS AosConfiguration.tSegment)) THEN RETURN END; COPY(entry.name,name); COPY(regRoot, key); Strings.AppendCh(key, "."); Strings.Append(key, name); (* Registry.GetKeyValue(Registry.CurrentUser, key, "Package", value); IF (Registry.res = Registry.Done) & (value # "") THEN *) IF AosConfiguration.GetKeyValue(Oberon.configuration,key, "Package", value) THEN COPY(regRoot, pkey); Strings.AppendCh(pkey, "."); Strings.Append(pkey, value); (* Registry.GetKeyValue(Registry.CurrentUser, pkey, "Version", value); IF Registry.res # Registry.Done THEN (* no package entry for name *) *) IF AosConfiguration.GetKeyValue(Oberon.configuration,pkey, "Version", value) THEN error := TRUE ELSE error := FALSE; F := Files.Old(name); IF F # NIL THEN Files.GetDate(F, ft, fd) ELSE COPY(name, findDefName); findDefD := 0; findDefT := 0; Compress.Enumerate("Definitions.Arc", FindDef, FALSE, res); IF (res = Compress.Done) & (findDefD # 0) THEN fd := findDefD; ft := findDefT ELSE error := TRUE END END; IF ~error THEN IF AosConfiguration.GetKeyValue(Oberon.configuration,key, "Date", value) THEN END; (* Registry.GetKeyValue(Registry.CurrentUser, key, "Date", value); *) Strings.StrToDate(value, rd); IF AosConfiguration.GetKeyValue(Oberon.configuration,key, "Time", value) THEN END; (* Registry.GetKeyValue(Registry.CurrentUser, key, "Time", value); *) Strings.StrToTime(value, rt); error := (fd < rd) OR ((fd = rd) & (ft < rt)) (* there is an older version installed *) END END; IF error THEN IF AosConfiguration.DeletePath(Oberon.configuration,key) THEN END; IF AosConfiguration.DeletePath(Oberon.configuration,pkey) THEN END; (* Registry.DeletePath(Registry.CurrentUser, key); Registry.DeletePath(Registry.CurrentUser, pkey); *) INC(errors) END ELSE (* must be a package *) END END CheckEntry; PROCEDURE Cleanup(); VAR segment: AosConfiguration.tEntry; BEGIN IF noReg THEN RETURN END; REPEAT errors := 0; segment := AosConfiguration.Find(Oberon.configuration,regRoot); AosConfiguration.EnumerateEntries(CheckEntry,segment); (* Registry.EnumeratePath(Registry.CurrentUser, regRoot, CheckEntry) *) UNTIL errors = 0 END Cleanup; PROCEDURE SystemName(VAR name: ARRAY OF CHAR); VAR prefixes: FileDir.FileName; i: LONGINT; BEGIN IF AosConfiguration.GetKeyValue(Oberon.configuration,"System","FilePrefixes", prefixes) & (prefixes # "")THEN (* Registry.GetKeyValue(Registry.CurrentUser, Registry.oberonSystem, "FilePrefixes", prefixes); IF (Registry.res = Registry.Done) & (prefixes # "") THEN *) i := 0; WHILE (prefixes[i] > " ") & (prefixes[i] # ";") DO INC(i) END; prefixes[i] := 0X; Strings.AppendCh(prefixes, "."); Strings.Append(prefixes, name); COPY(prefixes, name) END END SystemName; PROCEDURE ReadDocHeader(VAR R: Files.Rider; VAR ch: CHAR); VAR len: LONGINT; x, y, w, h: INTEGER; name: ARRAY 32 OF CHAR; BEGIN Files.Read(R, ch); Files.ReadString(R, name); Files.ReadInt(R, x); Files.ReadInt(R, y); Files.ReadInt(R, w); Files.ReadInt(R, h); Files.Read(R, ch); IF ch = 0F7X THEN (* skip meta info *) Files.Read(R, ch); ASSERT(ch = 08X); Files.ReadLInt(R, len); Files.Set(R, Files.Base(R), Files.Pos(R) + len); Files.Read(R, ch) END END ReadDocHeader; PROCEDURE OpenPackage(VAR pack: Package; F: Files.File): BOOLEAN; VAR R: Files.Rider; h: Compress.Header; i: INTEGER; attr, attrs: Attributes.Attr; links: Links.Link; ch: CHAR; BEGIN Files.Set(pack.R, F, 0); Files.ReadInt(pack.R, i); IF i # Documents.Id THEN COPY("not a document", pack.gen); RETURN FALSE END; Files.ReadString(pack.R, pack.gen); Files.ReadInt(pack.R, i); Files.ReadInt(pack.R, i); Files.ReadInt(pack.R, i); Files.ReadInt(pack.R, i); Files.Read(pack.R, ch); IF ch = 0F7X THEN (* skip meta info *) Documents.LoadAttachments(pack.R, attrs, links); attr := Attributes.FindAttr("Encryption", attrs); IF (attr # NIL) & (attr IS Attributes.BoolAttr) THEN useCrypt := attr(Attributes.BoolAttr).b END END; h.extensions := {}; h.length := Files.Length(F)-Files.Pos(pack.R); IF useCrypt THEN INCL(h.extensions, Compress.encryption) END; F := Files.New(""); Files.Set(R, F, 0); Compress.CopyFromArc(pack.R, R, h, i); IF i # Compress.Done THEN COPY("error in file", pack.gen); RETURN FALSE END; Files.Set(pack.R, F, 0); RETURN TRUE END OpenPackage; PROCEDURE CopyData(VAR from: Files.Rider; to: Files.File; len: LONGINT); VAR R: Files.Rider; buf: ARRAY 1024 OF CHAR; BEGIN Files.Set(R, to, 0); Files.ReadBytes(from, buf, 1024); WHILE len >= 1024 DO Files.WriteBytes(R, buf, 1024); Files.ReadBytes(from, buf, 1024); DEC(len, 1024) END; Files.WriteBytes(R, buf, len) END CopyData; PROCEDURE Backup(VAR res: Resource); VAR bak: FileDir.FileName; err: INTEGER; BEGIN IF Files.Old(res.name) # NIL THEN COPY(res.name, bak); Strings.Append(bak, ".Bak"); Files.Rename(res.name, bak, err) END END Backup; PROCEDURE CreateDirectory(name: ARRAY OF CHAR; VAR ok: BOOLEAN); VAR i, j: LONGINT; sub: FileDir.FileName; BEGIN i := 0; ok := TRUE; WHILE (name[i] # 0X) & ok DO j := 0; REPEAT sub[j] := name[i]; INC(i); INC(j) UNTIL (name[i] = 0X) OR (name[i] = FileDir.PathChar); IF name[i] = FileDir.PathChar THEN INC(i) END; IF j > 0 THEN sub[j] := 0X; Win32FS.ChangeDirectory(sub, ok); IF ~ok THEN FileDir.CreateDirectory(sub, ok); Win32FS.ChangeDirectory(sub, ok) END END END END CreateDirectory; (** Install the application package F. getURL (passing context as first parameter) is called when additional files are needed by install. Return values: 0: Installation susceeded, gen containes the generator for the default object if any. 1: Installation could not be finished, since some getURL requests are still pending. Retry install again when all getURL request are resolved. 2: F is not a valid package file. 3: A version conflict occurd while installing the package. See gen for the name of the conflicting resource. 4: Installation failed for some other reason. *) PROCEDURE install*(F: Files.File; VAR gen: ARRAY OF CHAR; context: Objects.Object; getURL: GetURLProc): LONGINT; VAR outputPath, workDir, options: FileDir.FileName; pack: Package; resl, res: ResourcePtr; R: Files.Rider; p, l, v, urls: LONGINT; i: INTEGER; ch: CHAR; restore: BOOLEAN; PROCEDURE StoreState(); BEGIN Win32FS.GetWorkingDirectory(workDir); COPY(OPM.outputPath, outputPath); restore := TRUE END StoreState; PROCEDURE RestoreState(); VAR done: BOOLEAN; BEGIN IF restore THEN Win32FS.ChangeDirectory(workDir, done); ASSERT(done); COPY(outputPath, OPM.outputPath); Texts.Append(Oberon.Log, W.buf); restore := FALSE END END RestoreState; PROCEDURE Error(msg1, msg2: ARRAY OF CHAR); VAR col: SHORTINT; BEGIN col := W.col; Texts.SetColor(W, 1); Texts.WriteString(W, msg1); Texts.WriteString(W, msg2); Texts.WriteLn(W); Texts.SetColor(W, col); COPY(msg1, gen); Strings.Append(gen, msg2); RestoreState(); Cleanup() END Error; PROCEDURE Warning(msg1, msg2: ARRAY OF CHAR); VAR col: SHORTINT; BEGIN col := W.col; Texts.SetColor(W, 3); Texts.WriteString(W, msg1); Texts.WriteString(W, msg2); Texts.WriteLn(W); Texts.SetColor(W, col) END Warning; PROCEDURE DoCmd(VAR cmd: Resource): BOOLEAN; VAR file, key, value: FileDir.FileName; i, j, v: LONGINT; res: ResourcePtr; cRes: Resource; F2, F3: Files.File; R, R2: Files.Rider; ok: BOOLEAN; T: Texts.Text; r: Texts.Reader; re: INTEGER; sym, col: SHORTINT; sep: CHAR; BEGIN Files.Set(R, F, cmd.pos); Files.ReadString(R, file); Files.ReadString(R, value); IF cmd.name # "MSG" THEN Texts.WriteString(W, cmd.name); Texts.Write(W, 09X); Texts.WriteString(W, file); Texts.Write(W, 09X); Texts.WriteString(W, value); Texts.WriteLn(W); Texts.Append(Oberon.Log, W.buf) END; res := resl; WHILE (res # NIL) & (res.name # file) DO res := res.next END; IF (res # NIL) & (cmd.name = "COMPILE") THEN Win32FS.GetWorkingDirectory(OPM.outputPath); Backup(cRes); Files.Set(R, F, res.pos); Files.ReadLInt(R, i); F2 := Files.New(""); CopyData(R, F2, i); Files.Set(R2, F2, 0); Files.Read(R2, ch); IF ch = 0F7X THEN ReadDocHeader(R2, ch) END; IF ch = Texts.TextBlockId THEN NEW(T); Texts.Open(T, ""); Texts.Load(T, F2, Files.Pos(R2), i); Texts.OpenReader(r, T, 0); OPM.Init({}, {}, r, Oberon.Log); OPS.Get := NIL; OPS.Init; OPS.Get(sym); OPS.Get(sym); ASSERT(sym = OPS.ident); COPY(OPS.name, cRes.name); Strings.Append(cRes.name, ".Obj"); v := CheckVersion(cRes, pack.options, TRUE); IF v IN {VerNewResource, VerNewBuild, VerNewMinorVersion} THEN ok := TRUE; OPS.Get := NIL; OPS.Init; Compiler.Module(r, cRes.name, value, 0, Oberon.Log, ok); IF ~ok THEN i := 0; j := 0; WHILE cRes.name[i] # 0X DO IF cRes.name[i] = FileDir.PathChar THEN j := 0 ELSE cRes.name[j] := cRes.name[i]; INC(j) END; INC(i) END; cRes.name[j] := 0X; RegisterResource(cRes, pack.name); RETURN TRUE END ELSIF ~(v IN {VerSameVersion, VerOldBuild, VerOldMinorVersion}) THEN Error(cRes.name, ": version conflict"); COPY(cRes.name, gen); RETURN FALSE END ELSE Error(res.name, ": not a valid text file"); COPY(res.name, gen); RETURN FALSE END ELSIF (res # NIL) & (cmd.name = "COPY") THEN v := CheckVersion(res^, pack.options, TRUE); IF v IN {VerNewResource, VerNewBuild, VerNewMinorVersion} THEN Backup(cRes); Files.Set(R, F, res.pos); Files.ReadLInt(R, i); F2 := Files.New(file); CopyData(R, F2, i); Files.Register(F2); RegisterResource(res^, pack.name) ELSIF ~(v IN {VerSameVersion, VerOldBuild, VerOldMinorVersion}) THEN Error(res.name, ": version conflict"); COPY(res.name, gen); RETURN FALSE END ELSIF (res # NIL) & (cmd.name = "DEF") THEN Strings.GetSuffix(file, value); cRes := res^; Files.Set(R, F, res.pos); Files.ReadLInt(R, i); file := "Temp.Def"; F2 := Files.New(file); CopyData(R, F2, i); IF value # "Def" THEN Files.Register(F2); NEW(T); Texts.Open(T, ""); Watson.MakeDef(file, T); COPY(file, cRes.name); Strings.Append(cRes.name, ".Def"); F2 := Files.New(""); Texts.Store(T, F2, 0, i) END; v := CheckVersion(cRes, pack.options, FALSE); IF v IN {VerNewResource, VerNewBuild, VerNewMinorVersion} THEN file := "Definitions.Arc"; F3 := Files.Old(file); IF F3 # NIL THEN Files.GetName(F3, file) ELSE Compress.CreateArchive(file, re); ASSERT(re = Compress.Done) END; Compress.DeleteFile(file, cRes.name, re); Files.Set(R2, F2, 0); Compress.AddFile(file, cRes.name, R2, Files.Length(F2), re); IF re = Compress.Done THEN RegisterResource(cRes, pack.name) ELSE Error(cRes.name, ": update of Definitions.Arc failed"); RETURN FALSE END ELSIF ~(v IN {VerSameVersion, VerOldBuild, VerOldMinorVersion}) THEN Error(cRes.name, ": version conflict"); COPY(cRes.name, gen); RETURN FALSE END ELSIF (cmd.name = "DEST") OR (cmd.name = "NEWDEST") THEN Win32FS.ChangeDirectory(workDir, ok); ASSERT(ok); Strings.Upper(file, file); i := 0; WHILE (i < NoPaths) & (paths[i].name # file) DO INC(i) END; IF (i < NoPaths) & (paths[i].path # "") THEN COPY(paths[i].path, file) END; Win32FS.ChangeDirectory(file, ok); IF ~ok & (cmd.name = "NEWDEST") THEN CreateDirectory(file, ok) END; IF ~ok THEN Error(file, ": invalid directory name"); END; RETURN ok ELSIF cmd.name = "MSG" THEN col := W.col; Texts.SetColor(W, 1); Texts.WriteString(W, file); Texts.WriteLn(W); Texts.Append(Oberon.Log, W.buf); Texts.SetColor(W, col) ELSIF cmd.name = "SET" THEN i := 0; j := 0; v := -1; WHILE file[i] # 0X DO IF (*(file[i] = ".") OR*) (file[i] = "\") THEN (* allow dots in key name *) file[i] := "\"; j := 0; v := i ELSE key[j] := file[i]; INC(j) END; INC(i) END; key[j] := 0X; IF v > 0 THEN file[v] := 0X END; (* Registry.OberonPath(file, file); Registry.SetKeyValue(Registry.CurrentUser, file, key, value); *) sep := AosConfiguration.PathSeparator; AosConfiguration.SetPathSeparator("\"); ok := AosConfiguration.SetKeyValue(Oberon.configuration,file,key,value); AosConfiguration.SetPathSeparator(sep); (* ok := Registry.res = Registry.Done; *) IF ~ok THEN Warning("Could not update Registry section:",file); Warning ("Key=",key); Warning("value=",value); END; RETURN ok ELSIF res = NIL THEN Error(file, ": missing from package"); COPY(file, gen); RETURN FALSE ELSE Error(cmd.name, ": not a valid command"); COPY(cmd.name, gen); RETURN FALSE END; RETURN TRUE END DoCmd; BEGIN COPY("", gen); restore := FALSE; IF ~OpenPackage(pack, F) THEN Error(pack.gen, ""); RETURN 2 END; (* Package Header *) F := Files.Base(pack.R); Files.Set(R, F, Files.Pos(pack.R)); p := 0; ReadResource(R, pack, i, l); IF i # HeadTag THEN Error("error in package file", ""); RETURN 2 END; Files.ReadString(R, pack.gen); Files.ReadString(R, pack.options); v := CheckVersion(pack, "", FALSE); IF (v IN {VerNewResource, VerNewBuild, VerNewMinorVersion}) OR ((v IN {VerSameVersion, VerOldBuild, VerOldMinorVersion}) & (pack.options = "A")) THEN (* URLs *) urls := 0; resl := NIL; NEW(res); WHILE ((p+l) < Files.Length(F)) DO p := p+l; Files.Set(R, F, p); ReadResource(R, res^, i, l); res.pos := Files.Pos(R); IF i = DataTag THEN res.next := resl; resl := res; NEW(res) ELSIF (i = URLTag) & ~noReg THEN Files.ReadString(R, res.name); Files.ReadString(R, options); IF options # "" THEN SystemName(res.name) END; (* \S *) v := CheckVersion(res^, "", FALSE); IF v IN {VerNewResource, VerNewBuild, VerNewMinorVersion} THEN v := getURL(context, res.name); INC(urls); IF v # 0 THEN Error(res.name, ": package file not found"); RETURN 4 END ELSIF ~(v IN {VerSameVersion, VerOldBuild, VerOldMinorVersion}) THEN Error(res.name, ": version conflict"); RETURN 3 END END END; IF urls = 0 THEN (* Install *) Files.Set(R, F, 0); p := 0; ReadResource(R, pack, i, l); Files.ReadString(R, pack.gen); Files.ReadString(R, pack.options); StoreState(); WHILE ((p+l) < Files.Length(F)) DO p := p+l; Files.Set(R, F, p); ReadResource(R, res^, i, l); res.pos := Files.Pos(R); IF i IN {DataTag, URLTag} THEN (* done *) ELSIF i = CmdTag THEN IF ~DoCmd(res^) THEN RETURN 4 END ELSE Error(res.name, ": invalid resource"); RETURN 2 END END; RegisterResource(pack, ""); RestoreState() ELSE RETURN 1 END ELSIF ~(v IN {VerSameVersion, VerOldBuild, VerOldMinorVersion}) THEN Error(pack.name, ": version conflict"); RETURN 3 END; COPY(pack.gen, gen); RETURN 0 END install; (** Create an object using the generator returned by install . *) PROCEDURE CreateObject*(gen: ARRAY OF CHAR; VAR err: ARRAY OF CHAR): Objects.Object; VAR D: Documents.Document; obj: Objects.Object; C: Objects.CopyMsg; prefix: ARRAY 8 OF CHAR; i, j: LONGINT; BEGIN obj := NIL; D := NIL; i := 0; WHILE (i < 8) & (gen[i] # ":") DO prefix[i] := gen[i]; INC(i) END; IF i < 8 THEN prefix[i] := 0X; INC(i); j := 0; WHILE gen[i] # 0X DO gen[j] := gen[i]; INC(i); INC(j) END; gen[j] := 0X; IF prefix = "obj" THEN obj := Gadgets.CreateObject(gen) ELSIF prefix = "doc" THEN IF gen[0] = "(" THEN j := 0; i := 1; WHILE (gen[i] # 0X) & (gen[i] # ")") DO gen[j] := gen[i]; INC(i); INC(j) END; gen[j] := 0X; obj := Gadgets.CreateObject(gen); IF (obj # NIL) & (obj IS Documents.Document) THEN D := obj(Documents.Document); D.Load(D) END ELSE D := Documents.Open(gen) END; IF (D # NIL) & (D.dsc # NIL) THEN obj := D END ELSIF Strings.Prefix("lib", prefix) THEN obj := Gadgets.FindPublicObj(gen); IF prefix = "libdeep" THEN C.id := Objects.deep ELSIF prefix = "libshallow" THEN C.id := Objects.shallow ELSE C.id := -1 END; IF (C.id # -1) & (obj # NIL) THEN C.obj := NIL; Objects.Stamp(C); obj.handle(obj, C); obj := C.obj END ELSE COPY(prefix, err); Strings.Append(err, ": invalid generator prefix") END; IF obj # NIL THEN COPY("", err); RETURN obj ELSE COPY(gen, err); Strings.Append(err, ": loading failed") END ELSE COPY(gen, err); Strings.Append(err, ": invalid generator") END; RETURN NIL END CreateObject; (* Implementation of an Application installer for local package files. *) PROCEDURE *GetURL(context: Objects.Object; url: ARRAY OF CHAR): LONGINT; VAR F: Files.File; gen: FileDir.FileName; res: LONGINT; BEGIN F := Files.Old(url); IF F = NIL THEN SystemName(url); F := Files.Old(url) END; IF F # NIL THEN Texts.WriteString(W, "installing "); Texts.WriteString(W, url); Texts.WriteLn(W); REPEAT res := install(F, gen, context, GetURL) UNTIL res # 1; Texts.WriteString(W, url); IF res # 0 THEN Texts.Write(W, 09X); Texts.WriteString(W, gen); Texts.WriteString(W, " ("); Texts.WriteInt(W, res, 0); Texts.Write(W, ")"); res := 3 ELSE Texts.WriteString(W, " done"); res := 0 END ELSE Texts.WriteString(W, url); Texts.WriteString(W, " not found"); res := 1 END; Texts.WriteLn(W); Texts.Append(Oberon.Log, W.buf); RETURN res END GetURL; (** Install packages available in your Oberon directories. For network installation of packages use HTMLPlugIns.Install. *) PROCEDURE Install*; VAR T: Texts.Text; S: Texts.Scanner; res: LONGINT; BEGIN OpenScanner(T, S); res := 0; WHILE (S.class IN {Texts.Name, Texts.String}) & (res = 0) DO res := GetURL(NIL, S.s); Texts.Scan(S) END; IF useCrypt THEN Compress.ClearCrypt(); useCrypt := FALSE END END Install; PROCEDURE *Load(D: Documents.Document); VAR F: Files.File; obj: Objects.Object; gen, err: FileDir.FileName; res: LONGINT; BEGIN D.dsc := NIL; Init(""); F := Files.Old(D.name); REPEAT res := install(F, gen, NIL, GetURL) UNTIL res # 1; IF res = 0 THEN obj := CreateObject(gen, err); IF obj # NIL THEN IF obj IS Documents.Document THEN D^ := obj(Documents.Document)^ ELSIF obj IS Display.Frame THEN WITH obj: Display.Frame DO D.dsc := obj; D.W := obj.W; D.H := obj.H END ELSE (* install ok, but no tool to display *) END ELSE (* install ok, but no tool to display *) END ELSE (* error reported by GetURL *) END END Load; PROCEDURE NewDoc*; VAR D: Documents.Document; BEGIN NEW(D); D.Load := Load; D.Store := NIL; D.handle := Documents.Handler; Objects.NewObj := D END NewDoc; PROCEDURE WriteRes(VAR W: Texts.Writer; VAR res: Resource); BEGIN Texts.WriteString(W, res.name); Texts.WriteString(W, " "); Texts.WriteInt(W, res.major, 0); Texts.Write(W, "."); Texts.WriteInt(W, res.minor, 0); Texts.WriteString(W, " "); Texts.WriteDate(W, res.time, res.date) END WriteRes; (** display the contents of a package file Packages.Directory *) PROCEDURE Directory*; VAR T: Texts.Text; S: Texts.Scanner; F: Files.File; R: Files.Rider; pack: Package; res: Resource; options, file, value: FileDir.FileName; p, l: LONGINT; i: INTEGER; BEGIN OpenScanner(T, S); IF S.class IN {Texts.Name, Texts.String} THEN F := Files.Old(S.s); IF F # NIL THEN IF OpenPackage(pack, F) THEN F := Files.Base(pack.R); Files.Set(R, F, Files.Pos(pack.R)); ReadResource(R, pack, i, l); ASSERT(i = HeadTag); p := 0; Files.ReadString(R, pack.gen); Files.ReadString(R, pack.options); Texts.WriteString(W, "PACKAGE "); WriteRes(W, pack); Texts.WriteString(W, ' "'); Texts.WriteString(W, pack.gen); Texts.Write(W, '"'); IF pack.options # "" THEN Texts.WriteString(W, " \"); Texts.WriteString(W, pack.options) END; Texts.WriteLn(W); WHILE ((p+l) < Files.Length(F)) DO p := p+l; Files.Set(R, F, p); ReadResource(R, res, i, l); Texts.Write(W, 09X); IF i = DataTag THEN Texts.WriteString(W, "DATA "); WriteRes(W, res) ELSIF i = URLTag THEN Files.ReadString(R, file); Files.ReadString(R, options); Texts.WriteString(W, "URL "); WriteRes(W, res); IF file # res.name THEN Texts.WriteString(W, " := "); Texts.WriteString(W, file) END; IF options # "" THEN Texts.WriteString(W, " \"); Texts.WriteString(W, options) END ELSIF i = CmdTag THEN Files.ReadString(R, file); Files.ReadString(R, value); Texts.WriteString(W, res.name); Texts.Write(W, " "); Texts.WriteString(W, file); IF value # "" THEN Texts.Write(W, " "); Texts.WriteString(W, value) END ELSE HALT(99) END; Texts.WriteLn(W) END ELSE Texts.WriteString(W, S.s); Texts.WriteString(W, pack.gen) END ELSE Texts.WriteString(W, S.s); Texts.WriteString(W, " not found") END; Texts.WriteLn(W); Texts.Append(Oberon.Log, W.buf) END; IF useCrypt THEN Compress.ClearCrypt(); useCrypt := FALSE END END Directory; (** extract individual files from a package file Packages.Extract { name [ "=>" file ] } "~" *) PROCEDURE Extract*; TYPE Name = POINTER TO NameDesc; NameDesc = RECORD name, file: FileDir.FileName; next: Name END; VAR T: Texts.Text; S: Texts.Scanner; F, F2: Files.File; R: Files.Rider; pack: Package; res: Resource; p, l, beg, end, time: LONGINT; i: INTEGER; prev, name, names: Name; BEGIN OpenScanner(T, S); IF S.class IN {Texts.Name, Texts.String} THEN F := Files.Old(S.s); Texts.Scan(S); IF (S.class = Texts.Char) & (S.c = "^") THEN Oberon.GetSelection(T, beg, end, time); Texts.OpenScanner(S, T, beg); Texts.Scan(S) END; names := NIL; WHILE ~S.eot & (S.class IN {Texts.Name, Texts.String}) DO NEW(name); COPY(S.s, name.name); COPY(S.s, name.file); name.next := names; names := name; Texts.Scan(S); IF (S.class = Texts.Char) & (S.c = "=") THEN Texts.Scan(S); IF (S.class = Texts.Char) & (S.c = ">") THEN Texts.Scan(S); IF S.class IN {Texts.Name, Texts.String} THEN COPY(S.s, name.file); Texts.Scan(S) END END END END; IF (F # NIL) & (names # NIL) THEN IF OpenPackage(pack, F) THEN Texts.WriteString(W, "Packages.Extract "); Texts.WriteString(W, pack.name); Texts.WriteLn(W); F := Files.Base(pack.R); Files.Set(R, F, Files.Pos(pack.R)); ReadResource(R, pack, i, l); ASSERT(i = HeadTag); p := 0; Files.ReadString(R, pack.gen); Files.ReadString(R, pack.options); WHILE (names # NIL) & ((p+l) < Files.Length(F)) DO p := p+l; Files.Set(R, F, p); ReadResource(R, res, i, l); res.pos := Files.Pos(R); IF i = DataTag THEN prev := NIL; name := names; WHILE (name # NIL) & (name.name # res.name) DO prev := name; name := name.next END; IF name # NIL THEN Texts.WriteString(W, " "); Texts.WriteString(W, name.name); IF prev # NIL THEN prev.next := name.next ELSE names := name.next END; Backup(res); Files.Set(R, F, res.pos); Files.ReadLInt(R, end); F2 := Files.New(name.file); CopyData(R, F2, end); Files.Register(F2); Texts.WriteString(W, " done"); Texts.WriteLn(W); END END END; name := names; WHILE name # NIL DO Texts.WriteString(W, " "); Texts.WriteString(W, name.name); Texts.WriteString(W, " not found"); Texts.WriteLn(W); name := name.next END; Texts.WriteLn(W); Texts.Append(Oberon.Log, W.buf) END END END; IF useCrypt THEN Compress.ClearCrypt(); useCrypt := FALSE END END Extract; PROCEDURE *PathEnumerator(path: ARRAY OF CHAR); VAR name: FileDir.FileName; i, j: LONGINT; done: BOOLEAN; BEGIN i := 0; j := -1; WHILE path[i] # 0X DO IF path[i] = FileDir.PathChar THEN j := i END; INC(i) END; INC(j); i := 0; WHILE path[j] # 0X DO name[i] := Strings.UpperCh(path[j]); INC(i); INC(j) END; name[i] := 0X; i := 0; WHILE (i < NoPaths) & (paths[i].name # name) DO INC(i) END; IF (i < NoPaths) & (paths[i].path = "") THEN Win32FS.GetWorkingDirectory(name); Win32FS.ChangeDirectory(path, done); Win32FS.GetWorkingDirectory(paths[i].path); Win32FS.ChangeDirectory(name, done) END END PathEnumerator; (* PROCEDURE OpenTool*; BEGIN (* System.CreateDirectory for all NEWDEST Packages.Extract for all copy commands (with path) Compiler.Compile for all COMPILE commands with options, check if file in copy list System.Set for all SET display MSG and remark for DEF *) END OpenTool; *) BEGIN Texts.OpenWriter(W); userPath := TRUE END Packages. Build / Install / Directory / Extract \C crypt key use Compress encryption \R ( "+" | "-" ) +: use registry, default -: ignore registry \U path create all files in path BIERìU:Z CSyntax10.Scn.Fnt28.05.03 11:34:06( CNameFindPublicObjTimeStamps.NewTextGadgets.NewControl