DIRECTORY Commander USING [CommandProc, CommandProcHandle, CommandProcObject, Enumerate, Handle, Lookup], CommandTool, CommandToolLookup USING [FindMatchingFiles], Convert USING [Error, RopeFromLiteral], FileNames USING [ConvertToSlashFormat, CurrentWorkingDirectory, Directory, FileWithSearchRules, ResolveRelativePath], FS USING [Error], IO USING [CreateStream, CreateStreamProcs, GetInfo, PutChar, PutF1, PutRope, RopeFromROS, ROS, STREAM], IOUtils USING [closedStreamProcs], List USING [AList, Append, Assoc, DotCons, Memb, PutAssoc, Remove], Process USING [CheckForAbort], ProcessProps USING [AddPropList, GetProp], ReadEvalPrint USING [Handle], Rope USING [Cat, Concat, Equal, Fetch, Length, Match, Replace, ROPE, Substr], RopeFile USING [Create], ViewerClasses USING [Viewer]; CommandToolUtilitiesImpl: CEDAR PROGRAM IMPORTS Commander, CommandTool, CommandToolLookup, Convert, FileNames, FS, IO, IOUtils, List, Process, ProcessProps, Rope, RopeFile EXPORTS CommandTool SHARES IO = BEGIN ROPE: TYPE = Rope.ROPE; commandFileProcData: Commander.CommandProcHandle _ NEW[Commander.CommandProcObject _ [CommandTool.CommandFile]]; GetViewer: PUBLIC PROC [cmd: Commander.Handle] RETURNS [ViewerClasses.Viewer] = { rep: ReadEvalPrint.Handle _ NIL; WHILE rep = NIL DO rep _ NARROW[GetProp[cmd, $ReadEvalPrintHandle]]; IF rep # NIL AND rep.viewer # NIL THEN RETURN[rep.viewer] ELSE rep _ NIL; cmd _ NARROW[GetProp[cmd, $ParentCommander]]; IF cmd = NIL THEN EXIT; ENDLOOP; RETURN[NIL]; }; GetReadEvalPrint: PUBLIC PROC [cmd: Commander.Handle, topLevel: BOOL _ FALSE] RETURNS [ReadEvalPrint.Handle] = { rep: ReadEvalPrint.Handle _ NIL; WHILE rep = NIL DO rep _ NARROW[GetProp[cmd, $ReadEvalPrintHandle]]; IF rep # NIL AND (NOT topLevel OR rep.topLevel) THEN EXIT ELSE rep _ NIL; cmd _ NARROW[GetProp[cmd, $ParentCommander]]; IF cmd = NIL THEN EXIT; ENDLOOP; RETURN[rep]; }; GetProp: PUBLIC PROC [cmd: Commander.Handle, key: REF] RETURNS [REF] = { RETURN [List.Assoc[key, cmd.propertyList]]; }; CopyAList: PUBLIC PROC [old: List.AList] RETURNS [new: List.AList] = { tail: List.AList _ NIL; new _ NIL; UNTIL old = NIL DO newItem: List.AList _ LIST[List.DotCons[key: old.first.key, val: old.first.val]]; IF tail = NIL THEN new _ newItem ELSE tail.rest _ newItem; old _ old.rest; tail _ newItem; ENDLOOP; }; PutLocalProperty: PUBLIC PROC [key, val: REF ANY, aList: List.AList, origList: List.AList _ NIL] RETURNS [List.AList] = { newList: List.AList; FOR l: List.AList _ aList, l.rest WHILE l # NIL AND l # origList DO IF l.first.key = key THEN { l.first.val _ val; RETURN[aList]; }; ENDLOOP; newList _ LIST[List.DotCons[key: key, val: val]]; newList.rest _ aList; RETURN[newList]; }; Insulate: PUBLIC PROC [stream: IO.STREAM] RETURNS [safeStream: IO.STREAM] = { safeStream _ IO.CreateStream[streamProcs: IO.CreateStreamProcs[variety: stream.GetInfo[].variety, class: stream.GetInfo[].class, close: IOUtils.closedStreamProcs.close], streamData: NIL, backingStream: stream]; }; CallList: PUBLIC PROC [property: REF ANY, cmd: Commander.Handle, proc: PROC [result: REF, msg: ROPE] RETURNS [stop: BOOL]] = { result: REF; msg: ROPE; WITH GetProp[cmd, property] SELECT FROM list: LIST OF REF ANY => FOR l: LIST OF REF ANY _ list, l.rest WHILE l # NIL DO WITH l.first SELECT FROM cpc: Commander.CommandProcHandle => { [result: result, msg: msg] _ cpc.proc[cmd]; IF proc # NIL AND proc[result: result, msg: msg] THEN EXIT; }; ENDCASE; ENDLOOP; ENDCASE; }; AddProcToList: PUBLIC PROC [aList: List.AList, listKey: REF ANY, proc: Commander.CommandProcHandle, append: BOOL _ TRUE] RETURNS [List.AList] = { maybeList: REF ANY _ List.Assoc[key: listKey, aList: aList]; list: LIST OF REF ANY; alreadyThere: BOOL; IF maybeList # NIL AND ISTYPE[maybeList, LIST OF REF ANY] THEN list _ NARROW[maybeList]; alreadyThere _ list # NIL AND List.Memb[ref: proc, list: list]; IF NOT alreadyThere THEN { IF append THEN { ra: REF ANY _ proc; list _ List.Append[list, LIST[ra]]; } ELSE list _ CONS[proc, list]; aList _ List.PutAssoc[key: listKey, val: list, aList: aList]; }; RETURN[aList]; }; RemoveProcFromList: PUBLIC PROC [aList: List.AList, listKey: REF ANY, proc: Commander.CommandProcHandle] RETURNS [List.AList] = { maybeList: REF ANY _ List.Assoc[key: listKey, aList: aList]; list: LIST OF REF ANY; alreadyThere: BOOL; IF maybeList # NIL AND ISTYPE[maybeList, LIST OF REF ANY] THEN list _ NARROW[maybeList]; alreadyThere _ list # NIL AND List.Memb[ref: proc, list: list]; IF alreadyThere THEN aList _ List.PutAssoc[key: listKey, val: List.Remove[ref: proc, list: list], aList: aList]; RETURN[aList]; }; FileWithSearchRules: PUBLIC PROC [root: ROPE, defaultExtension: ROPE, cmd: Commander.Handle, tryStar: BOOL _ TRUE] RETURNS [fullPath: ROPE _ NIL] = { RETURN[FileNames.FileWithSearchRules[root: root, defaultExtension: defaultExtension, requireExact: NOT tryStar, searchRules: GetProp[cmd, $SearchRules]].fullPath]; }; ResolveRelativePath: PUBLIC PROC [path: ROPE] RETURNS [ROPE] = { RETURN[FileNames.ResolveRelativePath[path]]; }; ConvertToSlashFormat: PUBLIC PROC [path: ROPE] RETURNS [ROPE] = { RETURN[FileNames.ConvertToSlashFormat[path]]; }; CopyListOfRefAny: PUBLIC PROC [key: REF ANY, aList: List.AList] RETURNS [List.AList] = { ref: REF ANY _ List.Assoc[key: key, aList: aList]; IF ref = NIL THEN RETURN[aList]; WITH ref SELECT FROM lra: LIST OF REF ANY => aList _ List.PutAssoc[key: key, val: List.Append[lra], aList: aList]; ENDCASE; RETURN[aList]; }; AddSearchRule: PUBLIC PROC [cmd: Commander.Handle, dir: ROPE, append: BOOL _ TRUE] = { rules: LIST OF REF ANY; length: INT; first, last: CHAR; rules _ NARROW[GetProp[cmd, $SearchRules]]; IF dir = NIL THEN rules _ NIL ELSE { dir _ FileNames.ResolveRelativePath[dir]; length _ dir.Length[]; IF length < 3 THEN RETURN; dir _ FileNames.ConvertToSlashFormat[dir]; first _ dir.Fetch[0]; last _ dir.Fetch[length - 1]; IF first # '/ THEN RETURN; IF last # '/ THEN dir _ Rope.Concat[dir, "/"]; FOR r: LIST OF REF ANY _ rules, r.rest WHILE r # NIL DO IF Rope.Equal[NARROW[r.first, ROPE], dir, FALSE] THEN RETURN; ENDLOOP; IF append THEN { ra: REF ANY _ dir; rules _ List.Append[rules, LIST[ra]]; } ELSE rules _ CONS[dir, rules]; }; [] _ List.PutAssoc[key: $SearchRules, val: rules, aList: cmd.propertyList]; }; CurrentWorkingDirectory: PUBLIC PROC RETURNS [ROPE] = { RETURN[FileNames.CurrentWorkingDirectory[]]; }; LookupWithSearchRules: PUBLIC Commander.CommandProc = { root: ROPE _ cmd.command; wDir: ROPE _ NIL; wDirRoot: ROPE _ NIL; temp: ROPE _ NIL; ambiguous: BOOL _ FALSE; rules: LIST OF REF ANY; fullPath: BOOL; Try: PROC [name: ROPE] RETURNS [foundSomething: BOOL] = { cmd.procData _ Commander.Lookup[name]; IF cmd.procData # NIL AND cmd.procData.proc # NIL THEN { cmd.command _ name; RETURN[TRUE]; }; cmd.procData _ NIL; RETURN[FALSE]; }; UniqueMatch: PROC [name: ROPE] RETURNS [foundSomething: BOOL _ FALSE] = { lst: LIST OF ROPE _ NIL; p: PROC [key: ROPE, procData: Commander.CommandProcHandle] RETURNS [stop: BOOL _ FALSE] = { IF procData.proc # NIL AND Rope.Match[pattern: name, object: key, case: FALSE] THEN lst _ CONS[key, lst]; }; name _ Rope.Concat[name, "*"]; [] _ Commander.Enumerate[p]; IF lst = NIL THEN RETURN[FALSE]; IF lst.rest # NIL THEN { ros: IO.STREAM _ IO.ROS[]; ambiguous _ TRUE; result _ $Ambiguous; ros.PutRope[" . . . command ambiguous ( "]; WHILE lst # NIL DO ros.PutRope[lst.first]; ros.PutChar[' ]; lst _ lst.rest; ENDLOOP; ros.PutRope[")\n"]; msg _ IO.RopeFromROS[ros]; RETURN[TRUE]; }; cmd.command _ lst.first; cmd.procData _ Commander.Lookup[cmd.command]; RETURN[TRUE]; }; IF root.Length[] = 0 THEN GO TO notFound; root _ FileNames.ResolveRelativePath[root]; root _ FileNames.ConvertToSlashFormat[root]; IF Try[root] THEN GO TO found; fullPath _ root.Fetch[0] = '/; IF NOT fullPath THEN { wDir _ FileNames.CurrentWorkingDirectory[]; wDirRoot _ Rope.Concat[wDir, root]; IF Try[wDirRoot] THEN GO TO found; rules _ NARROW[GetProp[cmd, $SearchRules]]; FOR list: LIST OF REF ANY _ rules, list.rest WHILE list # NIL DO IF Try[Rope.Concat[NARROW[list.first], root]] THEN GO TO found; ENDLOOP; }; IF ambiguous OR UniqueMatch[root] THEN GO TO found; IF NOT fullPath THEN { IF ambiguous OR UniqueMatch[wDirRoot] THEN GO TO found; FOR list: LIST OF REF ANY _ rules, list.rest WHILE list # NIL DO IF ambiguous OR UniqueMatch[Rope.Concat[NARROW[list.first], root]] THEN GO TO found; ENDLOOP; }; GO TO notFound; EXITS found => IF result # $Ambiguous THEN result _ $Found; notFound => result _ $Failed; }; CommandFileWithSearchRules: PUBLIC Commander.CommandProc = { commandFileName: ROPE _ NIL; ambiguous: BOOL _ GetProp[cmd, $Result] = $Ambiguous; list: LIST OF ROPE _ CommandToolLookup.FindMatchingFiles[root: cmd.command, defaultExtension: ".cm", requireExact: ambiguous, searchRules: GetProp[cmd, $SearchRules]]; IF list = NIL THEN GO TO notFound; commandFileName _ list.first; IF list.rest # NIL THEN { err: IO.STREAM _ cmd.err; IO.PutRope[err, "[[Ambiguous command files:\n"]; FOR each: LIST OF ROPE _ list, each.rest WHILE each # NIL DO IO.PutF1[err, " %g\n", [rope[each.first]] ]; ENDLOOP; IO.PutRope[err, " ]]\n"]; GO TO ambig; }; cmd.commandLine _ Rope.Concat[commandFileName, " "]; cmd.command _ "Commander"; cmd.procData _ commandFileProcData; result _ $Found; EXITS ambig => result _ $Ambiguous; notFound => result _ $Failed; }; LoadAndRunWithSearchRules: PUBLIC Commander.CommandProc = { commandFileName: ROPE; sc: SavedCommand _ NIL; list: LIST OF ROPE _ CommandToolLookup.FindMatchingFiles[root: cmd.command, defaultExtension: ".load", requireExact: FALSE, searchRules: GetProp[cmd, $SearchRules]]; IF list = NIL THEN GO TO notFound; commandFileName _ list.first; IF list.rest # NIL THEN { err: IO.STREAM _ cmd.err; result _ $Ambiguous; IO.PutRope[err, "[[Ambiguous load files:\n"]; FOR each: LIST OF ROPE _ list, each.rest WHILE each # NIL DO IO.PutF1[err, " %g\n", [rope[each.first]] ]; ENDLOOP; IO.PutRope[err, " ]]\n"]; GO TO ambig; }; cmd.procData _ NEW[Commander.CommandProcObject _ [ ReallyLoadAndRun, "Command not yet loaded", NEW[SavedCommandObject _ [command: cmd.command, commandFileName: commandFileName]]]]; result _ $Found; EXITS ambig => result _ $Ambiguous; notFound => result _ $Failed; }; SavedCommand: TYPE = REF SavedCommandObject; SavedCommandObject: TYPE = RECORD [ command: ROPE _ NIL, commandFileName: ROPE _ NIL ]; ReallyLookupHandle: Commander.CommandProcHandle _ NEW[Commander.CommandProcObject _ [ReallyLoadAndRun, "ReallyLoadAndRun", "execute .load file and run command"]]; ReallyLoadAndRun: Commander.CommandProc = { sc: SavedCommand _ NARROW[cmd.procData.clientData]; commandFileName: ROPE _ sc.commandFileName; originalCommandLine: ROPE _ cmd.commandLine; oldOut: IO.STREAM; oldIn: IO.STREAM; loadFileDirectory: ROPE; inner: PROC = { ENABLE UNWIND => {cmd.out _ oldOut; cmd.in _ oldIn}; [result, msg] _ CommandTool.CommandFile[cmd]; cmd.out _ oldOut; cmd.in _ oldIn; }; commandFileName _ FileNames.ConvertToSlashFormat[commandFileName]; loadFileDirectory _ FileNames.Directory[path: commandFileName]; cmd.command _ "CommandTool"; cmd.commandLine _ commandFileName; cmd.procData _ commandFileProcData; oldOut _ cmd.out; cmd.out _ Insulate[cmd.err]; oldIn _ cmd.in; cmd.in _ Insulate[oldIn]; ProcessProps.AddPropList[ List.PutAssoc[key: $WorkingDirectory, val: loadFileDirectory, aList: NIL], inner]; IF result = $Failure THEN RETURN[result, msg]; cmd.command _ sc.command; cmd.commandLine _ originalCommandLine; [result, msg] _ LookupWithSearchRules[cmd]; IF cmd.procData # NIL AND cmd.procData.proc # NIL THEN { CommandTool.ExecuteCommand[cmd, FALSE]; result _ CommandTool.GetProp[cmd, $Result]; } ELSE RETURN[$Failure, ".load file failed to register command"]; }; AtSignLimit: NAT _ 20; AtSignFile: PROC [name: ROPE] RETURNS [msg: ROPE _ NIL] = { WITH ProcessProps.GetProp[$CommanderHandle] SELECT FROM cmd: Commander.Handle => { rules: REF _ CommandTool.GetProp[cmd, $SearchRules]; paths: LIST OF ROPE _ CommandToolLookup.FindMatchingFiles[name, ".cm", TRUE, rules]; IF paths # NIL THEN name _ paths.first; }; ENDCASE => { RETURN [RopeFile.Create[name: Rope.Concat[name, ".cm"], raw: FALSE ! FS.Error => CONTINUE]]; }; RETURN [RopeFile.Create[name: name, raw: FALSE ! FS.Error => { msg _ error.explanation; GO TO fail}]]; EXITS fail => ERROR CommandTool.Failed[msg]; }; Pass1: PUBLIC PROC [initial: ROPE, nameOnly: BOOL] RETURNS [first: ROPE _ NIL, rest: ROPE _ NIL, terminator: CHAR _ '\n, someExpansion: BOOL _ FALSE] = { { state: {outside, insideQuotes, insideAtName} _ outside; c: CHAR; i: INT _ 0; atPosition: INT; atSignDepth: NAT _ 0; startPosition: INT _ 0; pastLeadingWhiteSpace: BOOL _ FALSE; IF NOT nameOnly THEN pastLeadingWhiteSpace _ TRUE; WHILE i < initial.Length[] DO Process.CheckForAbort[]; { c _ initial.Fetch[i]; IF c = '\\ THEN { slashPosition: INT _ i; slashSequenceLength: INT _ 2; escapeRope: ROPE; i _ i + 1; -- pointing at char after the slash IF i >= initial.Length[] THEN ERROR CommandTool.Failed["Backslash at end of command"]; c _ initial.Fetch[i]; -- get the char after the slash IF c IN ['0..'9] THEN { IF (i + 2) >= initial.Length[] THEN ERROR CommandTool.Failed["Not enough characters for backslash convention"]; slashSequenceLength _ 4; }; escapeRope _ Convert.RopeFromLiteral[Rope.Cat["""", initial.Substr[start: slashPosition, len: slashSequenceLength], """"] ! Convert.Error => ERROR CommandTool.Failed["Backslash sequence error"]; ]; initial _ Rope.Replace[base: initial, start: slashPosition, len: slashSequenceLength, with: escapeRope]; i _ slashPosition; c _ initial.Fetch[i]; }; SELECT state FROM outside => { SELECT c FROM '" => { state _ insideQuotes; pastLeadingWhiteSpace _ TRUE; }; ';, '\n, '| => { terminator _ c; GOTO FoundTerminator; }; '@ => { state _ insideAtName; atPosition _ i; }; ' , '\t => { IF nameOnly THEN { IF pastLeadingWhiteSpace THEN { terminator _ ' ; GOTO FoundTerminator; } ELSE startPosition _ startPosition + 1; }; }; '& => { IF nameOnly THEN { -- end of name terminator _ c; GOTO FoundTerminator; }; IF initial.Length[] > (i + 1) THEN { SELECT initial.Fetch[i+1] FROM ' , ' , '\n, '; => { -- followed by whitespace terminator _ c; GOTO FoundTerminator; }; ENDCASE => NULL; } ELSE { -- end of line terminator _ c; GOTO FoundTerminator; } }; ENDCASE => pastLeadingWhiteSpace _ TRUE; }; insideQuotes => { IF c = '" THEN { IF initial.Length[] > (i + 1) AND initial.Fetch[i+1] = '" THEN i _ i + 1 ELSE state _ outside; }; }; insideAtName => { IF c = '@ OR c = ' OR c = '\n THEN { nameLength: INT _ i - atPosition - 1; atSignFileName: ROPE _ initial.Substr[start: atPosition + 1, len: nameLength]; initial _ Rope.Replace[base: initial, start: atPosition, len: IF c = '@ THEN nameLength + 2 ELSE nameLength + 1, with: AtSignFile[name: atSignFileName]]; i _ atPosition - 1; state _ outside; atSignDepth _ atSignDepth + 1; IF atSignDepth > AtSignLimit THEN ERROR CommandTool.Failed["Exceeded limit on expansion of @ command files"]; someExpansion _ TRUE; }; }; ENDCASE => ERROR; i _ i + 1; }; REPEAT FoundTerminator => { rest _ Rope.Substr[base: initial, start: i + 1]; initial _ Rope.Substr[base: initial, start: startPosition, len: i - startPosition]; IF NOT nameOnly THEN initial _ Rope.Concat[initial, "\n"]; }; ENDLOOP; IF state = insideQuotes THEN ERROR CommandTool.Failed["Mismatched quotes"]; IF state = insideAtName THEN ERROR CommandTool.Failed["Improper @-file specification"]; }; first _ initial; }; END. October 7, 1983 9:38 am, Stewart, Created December 13, 1983 4:14 pm, Stewart, fixed bug in CallList January 14, 1984 7:45 pm, Stewart, fixed bugs in ReallyLoadAndRun úCommandToolUtilitiesImpl.mesa Copyright c 1984, 1985 by Xerox Corporation. All rights reserved. Larry Stewart, January 16, 1984 7:57 pm Swinehart, May 22, 1984 4:05:37 pm PDT Russ Atkinson (RRA) May 23, 1985 3:27:43 pm PDT Search up chain of parents to find a ReadEvalPrint.Handle and the associated commander CopyAList copies the CONS cells of the list itself and also copies the DotCons cells which are the elements of the list. Because the DotCons cells are copied, one can change the key-value mappings in the new list without affecting the mappings in the old list. Because the CONS cells are copied, one can alter the list without affecting the old list. PutLocalProperty is used to set a "local" property on a list. OrigList is intended to point into the middle of aList. If key is found on the part of aList in front of origList, then the old binding is changed and aList is returned. If key is not found on aList before origList is encountered, then a new property is added to the head of aList and the new list is returned. Insulate creates streams for which close appears to work, but which do not close the backing stream. It also attaches the in stream to the commander stop button. Several functions in the command tool use properties which are bound to lists of command procs. The following functions help to manage this arrangement. CallList uses the given property name (typically an ATOM) to search cmd.propertyList. The value of the property should be LIST OF REF ANY. Each of the elements of the list should resolve to a Commander.CommandProcHandle. Each of the command procs found will be called with cmd as its argument. If proc is not NIL, it is called with the return values from each of the CommandProcs called. proc can stop CallList from proceeding by returning stop = TRUE; IF result # $Preserve THEN cmd.propertyList _ List.PutAssoc[key: $Result, val: result, aList: cmd.propertyList]; AddProcToList is used to construct or alter a LIST OF REF ANY whose elements are actually Commander.CommandProcHandles. RemoveProcToList is used to remove a particular Commander.CommandProcHandle from a LIST OF REF ANY whose elements are actually Commander.CommandProcHandles. If path starts with ./ or ../, ResolveRelativePath converts it into the equivalent full path name using the $WorkingDirectory property on the process properties list. If path is exactly . or .., ResolveRelativePath converts it to the current or parent directory. CopyListOfRefAny searches for the binding of the given key. If it is a LIST OF REF ANY, then it is List.Append-ed and put back. If rule = NIL THEN deletes all search rules. If append = FALSE, then dir becomes the first rule suppress duplicates ambiguous prefix Exactly one match try root try Concat[$WorkingDirectory, root] For each search rule try Concat[search rule, root] try Concat[search rule, root] try to find a unique match for Concat[root, "*"]; try to find a unique match for Cat[$WorkingDirectory, root, "*"]; For each search rule try to find a unique match for Cat[search rule, root, "*"]; CommandFileWithSearchRules: look in the FS directory for a command file If $Result is $Ambiguous on entry, then only try exact matches Return $Ambiguous if FileWithSearchRules does LoadAndRunWithSearchRules: look in the FS directory for a file with extension ".load" If found, run it then attempt a normal commander lookup (using LookupWithSearchRules). If $Result is $Ambiguous on entry, then only try exact matches Return $Ambiguous if FileWithSearchRules does We have located a command file. These are used when LoadAndRun has to save the original command information procData.clientData is a SavedCommand, load the .load file whose name is in commandLine and then Lookup and execute the saved command. set the working directory for this call (and this call only) Success. So pass along the result of executing the command. Pass1 handles the initial rope passed in from ReadEvalPrint. It searches for the first ';, if there is one, and restricts its attention to the part of the rope before it. ÊÚ˜codešœ™Kšœ Ïmœ7™BKšœ'™'K™&K™/—K˜šÏk ˜ Kšœ žœP˜_Kšœ ˜ Kšœžœ˜,Kšœžœ˜'Kšœ žœf˜uKšžœžœ ˜KšžœžœRžœžœ˜gKšœžœ˜"Kšœžœ9˜CKšœžœ˜Kšœ žœ˜*Kšœžœ ˜Kšœžœ5žœ ˜MKšœ žœ ˜Kšœžœ ˜—K˜šœžœž˜'Kšžœ@žœžœ6˜ƒKšžœ ˜Kšžœž˜ Kšœž˜K˜Kšžœžœžœ˜—™Kšœ3žœ:˜pK˜—šÏn œžœžœžœ˜QKšœžœ˜ šžœžœž˜Kšœžœ%˜1Kš žœžœžœžœžœžœ ˜9Kšžœžœ˜Kšœžœ!˜-Kšžœžœžœž˜Kšžœ˜—Kšžœžœ˜ Kšœ˜K˜—š Ÿœžœžœ#žœžœžœ˜pKšœžœ˜ KšœV™Všžœžœž˜Kšœžœ%˜1Kšžœžœžœžœ žœžœž œžœ˜IKšœžœ!˜-Kšžœžœžœžœ˜Kšžœ˜—Kšžœ˜ Kšœ˜K˜—š Ÿœžœžœžœžœžœ˜HKšžœ%˜+K˜—K˜šŸ œžœžœžœ˜FKšœžœùžœI™ßKšœžœ˜Kšœžœ˜ šžœžœž˜Kšœžœ7˜QKšžœžœžœžœ˜:Kšœ˜Kšœ˜Kšžœ˜—K˜—K˜šŸœžœžœ žœžœ,žœžœ˜yKšœ÷™÷Kšœ˜š žœžœžœžœž˜Cšžœžœ˜Kšœ˜Kšžœ˜K˜—Kšžœ˜—Kšœ žœ#˜1K˜Kšžœ ˜K˜—K˜šŸœžœžœ žœžœžœžœžœ˜MK™¢Kšœ žœžœŠžœ˜ÒK˜—K˜K™™K™šŸœžœžœ žœžœžœ žœžœžœžœ˜~KšœÉ™ÉKšœžœ˜ Kšœžœ˜ šžœžœž˜'š œžœžœžœžœ˜šžœžœžœžœžœžœžœž˜6šžœ žœž˜šœ%˜%Kšœ+˜+šžœž™šœ™KšœB™B——Kš žœžœžœ žœžœ˜;K˜—Kšžœ˜—Kšžœ˜——Kšžœ˜—K˜—K˜šŸ œžœžœžœžœ-žœžœžœ˜‘Kš œ.žœžœžœžœ:™wKšœ žœžœ*˜Kšœ-™-—Kšœžœžœ˜Kšœ žœ&˜5Kšœžœžœžœ•˜§Kš žœžœžœžœžœ ˜"Kšœ˜šžœ žœžœ˜Kšœžœžœ ˜Kšžœ.˜0š žœžœžœžœžœžœž˜Kšœ-™-Kšœžœ˜Kšœžœ˜Kš œžœžœžœcžœ+˜¥Kš žœžœžœžœžœ ˜"Kšœ˜šžœ žœžœ˜Kšœžœžœ ˜Kšœ˜Kšžœ+˜-š žœžœžœžœžœžœž˜˜>——K˜šž˜Kšœ˜K˜—K˜K˜—K™KKšœžœžœ˜,K˜šœžœžœ˜#Kšœ žœžœ˜Kšœžœž˜K˜—K˜Kšœ2žœm˜¢K˜šœ+˜+Kšœ†™†Kšœžœ˜3Kšœžœ˜+Kšœžœ˜,Kšœžœžœ˜Kšœžœžœ˜Kšœžœ˜šœžœ˜Kšžœžœ'˜4Kšœ-˜-Kšœ˜Kšœ˜Kšœ˜—KšœB˜BKšœ?˜?Kšœ˜Kšœ"˜"K˜#K˜Kšœ˜K˜Kšœ˜K™<šœ˜KšœEžœ˜JKšœ˜—Kšžœžœžœ˜.Kšœ˜Kšœ&˜&Kšœ+˜+šžœžœžœž˜1šžœ˜Kšœ<™žœžœžœ9˜™K˜Kšœ˜K˜šžœž˜!KšžœF˜K—Kšœžœ˜K˜—K˜—Kšžœžœ˜—K˜ K˜Kšž˜˜Kšœ0˜0KšœS˜SKšžœžœ žœ'˜;K˜—Kšžœ˜—Kšžœžœžœ)˜KKšžœžœžœ5˜WK˜K˜K˜—K˜Kšžœ˜K˜K˜)K˜9K˜A—…—<æcº