<<>> <> <> <> <> <<(Changed treatment of new messages)>> <> <> <<>> <> <> <> DIRECTORY BasicTime USING [nullGMT, GMT, Now], Convert, <> IO, MailParse USING [endOfInput], LoganBerry, LoganBerryEntry, Rope, RuntimeError USING [BoundsFault], SendMailOps USING [IsThisTheCurrentUser], UserProfile USING [Boolean, CallWhenProfileChanges, ProfileChangedProc], VFonts USING [CharWidth, StringWidth], ViewerTools USING [TiogaContents], WalnutDefs USING [Error, MsgSet, SeFromToCcSuDaMid, WalnutOpsHandle], WalnutDB USING [ ChangeCountOfMsgs, ChangeCountInMsgSet, EntryObject, EntryRef, GeneralEnumerator, GeneralEnumeratorRec, GetMsgDisplayInfo], WalnutKernelDefs USING [MsgLogEntry], WalnutSchema; WalnutDBMsgImpl: CEDAR PROGRAM IMPORTS BasicTime, Convert, IO, LoganBerry, LoganBerryEntry, Rope, RuntimeError, SendMailOps, UserProfile, VFonts, WalnutDB, WalnutDefs EXPORTS WalnutDB, WalnutDefs = BEGIN OPEN WalnutDB, WalnutSchema; <<>> <> ROPE: TYPE = Rope.ROPE; GMT: TYPE = BasicTime.GMT; TiogaContents: TYPE = ViewerTools.TiogaContents; Relship: TYPE = LoganBerry.Entry; Address: TYPE = ROPE; Subject: TYPE = ROPE; Keyword: TYPE = ROPE; WalnutOpsHandle: TYPE = WalnutDefs.WalnutOpsHandle; SchemaHandle: TYPE = WalnutSchema.SchemaHandle; SchemaHandleRec: PUBLIC TYPE = WalnutSchema.SchemaHandleRec; MsgSet: TYPE = WalnutDefs.MsgSet; MsgLogEntry: TYPE = WalnutKernelDefs.MsgLogEntry; <<>> <> useFromFieldInTOC: BOOL ¬ UserProfile.Boolean[key: "Walnut.UseFromFieldInTOC", default: FALSE]; blankWidth: INT ¬ VFonts.CharWidth[' ]; -- in default font blanks: ROPE ¬ " "; -- lotsa blanks <<>> <> IsEntity: TYPE = RECORD [entity: LoganBerry.Entry, exists: BOOL]; NilEntity: IsEntity = [NIL, FALSE]; GeneralEnumerator: TYPE = WalnutDB.GeneralEnumerator; <<>> <> <> <<>> <> <> <> <<>> MsgExists: PUBLIC PROC[opsH: WalnutOpsHandle, msg: ROPE] RETURNS [exists: BOOL] = <> { RETURN[GetMsgEntity[opsH, msg].exists] }; <> GetIsInReplyTo: PUBLIC PROC[opsH: WalnutOpsHandle, msg: ROPE] RETURNS[isInReplyTo: BOOL] = { <> sH: WalnutSchema.SchemaHandle = opsH.schemaHandle; m: IsEntity = GetMsgEntity[opsH, msg]; IF ~m.exists THEN RETURN[FALSE]; isInReplyTo ¬ LoganBerryEntry.V2B[LoganBerryEntry.GetAttr[m.entity, sH.mMIIsInReplyTo]]; }; GetHasBeenRead: PUBLIC PROC[opsH: WalnutOpsHandle, msg: ROPE] RETURNS[has: BOOL] = { <> sH: WalnutSchema.SchemaHandle = opsH.schemaHandle; m: IsEntity = GetMsgEntity[opsH, msg]; IF ~m.exists THEN { has ¬ FALSE; RETURN }; has ¬ LoganBerryEntry.V2B[LoganBerryEntry.GetAttr[LoganBerry.ReadEntry[db: opsH.db, key: $Key, value: Rope.Concat[sH.mDisplayInfo, msg]].entry, sH.mDIHasBeenRead]]; }; SetHasBeenRead: PUBLIC PROC[opsH: WalnutOpsHandle, msg: ROPE] = { <> sH: WalnutSchema.SchemaHandle = opsH.schemaHandle; m: IsEntity = GetMsgEntity[opsH, msg]; md: LoganBerry.Entry; IF ~m.exists THEN RETURN; md ¬ LoganBerry.ReadEntry[db: opsH.db, key: $Key, value: Rope.Concat[sH.mDisplayInfo, msg]].entry; LoganBerryEntry.SetAttr[md, sH.mDIHasBeenRead, LoganBerryEntry.B2V[TRUE]]; LoganBerry.WriteEntry[db: opsH.db, entry: md, replace: TRUE]; }; AddNewMsg: PUBLIC PROC[opsH: WalnutOpsHandle, msg: MsgLogEntry] RETURNS[mExisted: BOOL] = { <> msgEName: ROPE = msg.msg; sH: SchemaHandle = opsH.schemaHandle; m: IsEntity = GetMsgEntity[opsH, msgEName]; IF mExisted ¬ m.exists THEN RETURN; -- id's assumed unique BEGIN me: ROPE = msgEName; herald, tocHead: ROPE; shortNameLen: INT; date: BasicTime.GMT = msg.date; sender: Address; [herald, tocHead, shortNameLen] ¬ ComputeHeraldAndTOC[msg]; sender ¬ SetAddresses[opsH, me, msg]; SetAllMsgInfo[opsH, me, msg, herald, shortNameLen, date, sender]; SetMsgDisplayInfo[opsH, me, FALSE, tocHead]; SetMsgInfo[opsH, me, msg.date, msg.show]; WalnutDB.ChangeCountOfMsgs[opsH, 1]; BEGIN -- put it in active - no checking necessary init: LoganBerry.Entry = LIST[ [$Key, Rope.Concat[sH.cdRelation, me]], [sH.cdMsg, me], [sH.cdMsgSet, opsH.schemaHandle.activeEntity], [sH.cdDate, LoganBerryEntry.T2V[msg.date]] ]; LoganBerry.WriteEntry[db: opsH.db, entry: init]; IF msg.show THEN WalnutDB.ChangeCountInMsgSet[opsH, opsH.schemaHandle.activeEntity, 1]; END; END; }; GetMsgEntryPosition: PUBLIC PROC[opsH: WalnutOpsHandle, msg: ROPE] RETURNS[pos: INT] = { <> sH: WalnutSchema.SchemaHandle = opsH.schemaHandle; m: IsEntity = GetMsgEntity[opsH, msg]; IF ~m.exists THEN {pos ¬ -1; RETURN}; pos ¬ LoganBerryEntry.V2I[LoganBerryEntry.GetAttr[LoganBerry.ReadEntry[db: opsH.db, key: $Key, value: Rope.Concat[sH.mMsgInfo, msg]].entry, sH.mMIEntryStart] ]; }; SetMsgEntryPosition: PUBLIC PROC[opsH: WalnutOpsHandle, to: INT] = { <> sH: WalnutSchema.SchemaHandle = opsH.schemaHandle; IF sH.lastMsgEntity = NIL THEN RETURN; LoganBerryEntry.SetAttr[sH.lastMsgEntity, sH.mMIEntryStart, LoganBerryEntry.I2V[to]]; LoganBerry.WriteEntry[db: opsH.db, entry: sH.lastMsgEntity, replace: TRUE]; }; GetMsgDate: PUBLIC PROC[opsH: WalnutOpsHandle, msg: ROPE] RETURNS[date: GMT] = { <> sH: WalnutSchema.SchemaHandle = opsH.schemaHandle; m: IsEntity = GetMsgEntity[opsH, msg]; IF ~m.exists THEN { date ¬ BasicTime.nullGMT; RETURN} ELSE { rel: Relship = LoganBerry.ReadEntry[db: opsH.db, key: $Key, value: Rope.Concat[sH.mInfo, msg]].entry; date ¬ LoganBerryEntry.V2T[LoganBerryEntry.GetAttr[rel, sH.mDateIs]]; }; }; GetMsgTextInfo: PUBLIC PROC[opsH: WalnutOpsHandle, msg: ROPE] RETURNS[textStart, textLen, formatLen: INT] = { <> m: IsEntity = GetMsgEntity[opsH, msg]; IF ~m.exists THEN { textStart ¬ textLen ¬ formatLen ¬ 0; RETURN; }; [textStart, textLen, formatLen] ¬ GetTextInfo[opsH.schemaHandle, m.entity]; }; GetMsgText: PUBLIC PROC[opsH: WalnutOpsHandle, msg: ROPE] RETURNS[textStart, textLen, formatLen: INT, herald: ROPE, shortNameLen: INT] = { <> m: IsEntity = GetMsgEntity[opsH, msg]; IF ~m.exists THEN { textStart ¬ textLen ¬ formatLen ¬ shortNameLen ¬ 0; RETURN; }; [textStart, textLen, formatLen, herald, shortNameLen] ¬ GetAllMsgTextInfo[opsH.schemaHandle, m.entity]; }; GetDisplayProps: PUBLIC PROC[opsH: WalnutOpsHandle, msg: ROPE] RETURNS [hasBeenRead: BOOL ¬ TRUE, tocEntry: ROPE ¬ NIL, startOfSubject: INT ¬ 0] = { <> sH: WalnutSchema.SchemaHandle = opsH.schemaHandle; m: IsEntity = GetMsgEntity[opsH, msg]; IF m.exists THEN [hasBeenRead, tocEntry, startOfSubject] ¬ WalnutDB.GetMsgDisplayInfo[opsH, LoganBerryEntry.GetAttr[m.entity, sH.mMIOf]]; }; GetCategories: PUBLIC PROC[opsH: WalnutOpsHandle, msg: ROPE] RETURNS[msList: LIST OF ROPE] = { <> sH: WalnutSchema.SchemaHandle = opsH.schemaHandle; m: IsEntity = GetMsgEntity[opsH, msg]; rel: Relship; IF ~m.exists THEN RETURN; rel ¬ LoganBerry.ReadEntry[db: opsH.db, key: $Key, value: Rope.Concat[sH.cdRelation, msg]].entry; msList ¬ LoganBerryEntry.GetAllAttrs[rel, sH.cdMsgSet]; }; SizeOfDatabase: PUBLIC PROC[opsH: WalnutOpsHandle] RETURNS[messages, msgSets: INT] = { sH: WalnutSchema.SchemaHandle = opsH.schemaHandle; rVersionInfo: Relship = LoganBerry.ReadEntry[db: opsH.db, key: $Key, value: sH.gVersionInfo].entry; messages ¬ LoganBerryEntry.V2I[LoganBerryEntry.GetAttr[rVersionInfo, sH.gMsgCount]]; msgSets ¬ LoganBerryEntry.V2I[LoganBerryEntry.GetAttr[rVersionInfo, sH.gMsgSetCount]]; }; GenerateEntriesPlusDate: PUBLIC PROC [opsH: WalnutOpsHandle, attr: ATOM, start: ROPE ¬ NIL, end: ROPE ¬ NIL, dateStart: ROPE ¬ NIL, dateEnd: ROPE ¬ NIL] RETURNS [genEnum: GeneralEnumerator ] = { <> IndexedField: PROC [db: LoganBerry.OpenDB, key: LoganBerry.AttributeType] RETURNS [BOOLEAN ¬ FALSE] ~ { schema: LoganBerry.SchemaInfo ¬ LoganBerry.Describe[NIL, db]; FOR i: LIST OF LoganBerry.IndexInfo ¬ schema.indices, i.rest WHILE i # NIL DO IF i.first.key = key THEN RETURN[TRUE]; ENDLOOP; RETURN[FALSE]; }; GenerateEntriesOrFullScan: PROC [db: LoganBerry.OpenDB, key: LoganBerry.AttributeType, start: LoganBerry.AttributeValue, end: LoganBerry.AttributeValue] RETURNS [cursor: LoganBerry.Cursor] ~ { IF key#NIL AND IndexedField[db, key] THEN cursor ¬ LoganBerry.GenerateEntries[db: db, key: key, start: start, end: end] ELSE cursor ¬ LoganBerry.GenerateEntries[db: db, key: $Key, start: sH.mMsgInfo, end: Rope.Concat[sH.mMsgInfo, "\255"]]; -- full scan }; sH: WalnutSchema.SchemaHandle = opsH.schemaHandle; genEnum ¬ NEW[WalnutDB.GeneralEnumeratorRec]; genEnum.opsH ¬ opsH; SELECT attr FROM $Key => { genEnum.cursor ¬ GenerateEntriesOrFullScan[db: opsH.db, key: NIL, start: start, end: end]; }; $MsgSetName => { <> genEnum.cursor ¬ GenerateEntriesOrFullScan[db: opsH.db, key: NIL, start: start, end: end]; }; $MessageText => { genEnum.cursor ¬ GenerateEntriesOrFullScan[db: opsH.db, key: NIL, start: start, end: end]; }; $From => { genEnum.cursor ¬ GenerateEntriesOrFullScan[db: opsH.db, key: NIL, start: start, end: end]; }; $To => { genEnum.cursor ¬ GenerateEntriesOrFullScan[db: opsH.db, key: NIL, start: start, end: end]; }; $Cc => { genEnum.cursor ¬ GenerateEntriesOrFullScan[db: opsH.db, key: NIL, start: start, end: end]; }; $Date => { genEnum.cursor ¬ GenerateEntriesOrFullScan[db: opsH.db, key: sH.mMIDate, start: start, end: end]; }; $Sender => { genEnum.cursor ¬ GenerateEntriesOrFullScan[db: opsH.db, key: sH.mMISender, start: start, end: end]; }; $Subject => { genEnum.cursor ¬ GenerateEntriesOrFullScan[db: opsH.db, key: sH.mMISubject, start: start, end: end]; }; ENDCASE => ERROR WalnutDefs.Error[$db, $invalidQueryAttr, IO.PutFR1["Attribute is %g", [atom[attr]]] ]; }; NextEntry: PUBLIC PROC[genEnum: GeneralEnumerator] RETURNS[entry: EntryRef ¬ NIL] = { sH: WalnutSchema.SchemaHandle = genEnum.opsH.schemaHandle; msgID: ROPE; e: LoganBerry.Entry ¬ LoganBerry.NextEntry[cursor: genEnum.cursor]; IF e = NIL THEN RETURN[NIL]; msgID ¬ LoganBerryEntry.GetAttr[e, sH.mMIOf]; entry ¬ NEW[WalnutDB.EntryObject]; entry.seFromToCcSuDaMid.sender ¬ LoganBerryEntry.GetAttr[e, sH.mMISender]; entry.seFromToCcSuDaMid.from ¬ GetFromList[genEnum.opsH, msgID]; entry.seFromToCcSuDaMid.to ¬ GetToList[genEnum.opsH, msgID]; entry.seFromToCcSuDaMid.cc ¬ GetCcList[genEnum.opsH, msgID]; entry.seFromToCcSuDaMid.keyword ¬ NIL; entry.seFromToCcSuDaMid.subject ¬ LoganBerryEntry.GetAttr[e, sH.mMISubject]; entry.seFromToCcSuDaMid.fullSubjectText ¬ LoganBerryEntry.GetAttr[e, sH.mMISubjectText]; entry.seFromToCcSuDaMid.date ¬ LoganBerryEntry.GetAttr[e, sH.mMIDate]; entry.seFromToCcSuDaMid.msgID ¬ msgID; entry.msgSetName ¬ GetCategories[genEnum.opsH, msgID].first; -- this probably isn't right }; <<>> <> ComputeHeraldAndTOC: PROC[mle: MsgLogEntry] RETURNS[herald, tocHead: ROPE, shortNameLen: INT] = { date, tocx: ROPE; starter: ROPE = IF useFromFieldInTOC THEN mle.from ELSE mle.sender; from: ROPE = IF SendMailOps.IsThisTheCurrentUser[starter] THEN Rope.Concat["To: ", mle.to] ELSE starter; IF mle.date = BasicTime.nullGMT THEN mle.date ¬ BasicTime.Now[]; date ¬ Rope.Substr[Convert.RopeFromTimeRFC822[mle.date], 0, 9]; tocx ¬ date.Cat[" ", from]; tocHead ¬ SquashRopeIntoWidth[tocx, 165]; herald ¬ RemoveComments[from]; herald ¬ Rope.Cat[herald, " ", date]; shortNameLen ¬ herald.Length[]; herald ¬ Rope.Cat[herald, " ", mle.subject]; IF herald.Length[] > 60 THEN herald ¬ Rope.Concat[herald.Substr[0, 55], " ..."]; IF herald.Length[] < shortNameLen THEN shortNameLen ¬ herald.Length[]; }; SquashRopeIntoWidth: PROC[s: ROPE, colWidth: INT] RETURNS[ROPE] = <> <> BEGIN blankCount: INT; width: INT; BEGIN ENABLE RuntimeError.BoundsFault => GOTO doItTheHardWay; width¬ VFonts.StringWidth[s]; DO IF width<= colWidth THEN EXIT; -- truncate BEGIN guessLength: INT¬ s.Length[] * colWidth / width; s¬ Rope.Concat[s.Substr[0, MAX[0, guessLength-4]], "..."]; width¬ VFonts.StringWidth[s]; END; ENDLOOP; EXITS doItTheHardWay => [width, s]¬ DoItTheHardWay[s, colWidth]; END; -- of enable -- At this point s is shorter than colWidth and we want to extend it with blanks blankCount¬ ((colWidth - width) / blankWidth) + 1; -- force at least one blank s¬ Rope.Concat[s, Rope.Substr[blanks, 0, MIN[blankCount, blanks.Length[]]]]; RETURN[s] END; DoItTheHardWay: PROC[s: ROPE, colWidth: INT] RETURNS[width: INT, s1: ROPE] = { thisWidth: INTEGER; dots: ROPE = "..."; nullWidth: INTEGER = VFonts.CharWidth['\000]; width¬ VFonts.StringWidth[dots]; FOR i: INT IN [0 .. s.Length[]) DO thisWidth¬ VFonts.CharWidth[s.Fetch[i] ! RuntimeError.BoundsFault => thisWidth¬ nullWidth ]; width¬ width + thisWidth; IF width > colWidth THEN { width¬ width - thisWidth; s1¬ Rope.Concat[s.Substr[0, MAX[0, i-1]], dots]; RETURN }; ENDLOOP; s1¬ s.Concat[dots]; }; RemoveComments: PROC[name: ROPE] RETURNS[shortName: ROPE] = { start, end: INT; name ¬ Rope.Concat[base: name, rest: " "]; <" in the name>> start ¬ Rope.Find[name, "<"]; IF start > 0 THEN { end ¬ Rope.Find[s1: name, s2: ">", pos1: start+1]; IF end > 0 THEN name ¬ Rope.Replace[name, start, end-start+1] }; <> start ¬ Rope.Find[name, "("]; IF start > 0 THEN { end ¬ Rope.Find[s1: name, s2: ")", pos1: start+1]; IF end > 0 THEN name ¬ Rope.Replace[name, start, end-start+1] }; shortName ¬ Rope.Substr[name, 0, Rope.Length[name]-1] }; GetMsgEntity: PROC[opsH: WalnutOpsHandle, msg: ROPE] RETURNS[e: IsEntity] = { sH: WalnutSchema.SchemaHandle = opsH.schemaHandle; IF msg.Length[] = 0 THEN { sH.lastMsgEntity ¬ NIL; RETURN[NilEntity]; }; sH.lastMsgEntity ¬ e.entity ¬ LoganBerry.ReadEntry[db: opsH.db, key: $Key, value: Rope.Concat[sH.mMsgInfo, msg]].entry; e.exists ¬ sH.lastMsgEntity # NIL; }; GetAllMsgTextInfo: PROC[sH: SchemaHandle, m: LoganBerry.Entry] RETURNS[textStart, textLen, formatLen: INT, herald: ROPE, shortNameLen: INT] = { rel: Relship = m; textStart ¬ LoganBerryEntry.V2I[LoganBerryEntry.GetAttr[rel, sH.mMIEntryStart]] + LoganBerryEntry.V2I[LoganBerryEntry.GetAttr[rel, sH.mMITextOffset]]; textLen ¬ LoganBerryEntry.V2I[LoganBerryEntry.GetAttr[rel, sH.mMITextLen]]; formatLen ¬ LoganBerryEntry.V2I[LoganBerryEntry.GetAttr[rel, sH.mMIFormatLen]]; herald ¬ LoganBerryEntry.GetAttr[rel, sH.mMIHerald]; shortNameLen ¬ LoganBerryEntry.V2I[LoganBerryEntry.GetAttr[rel, sH.mMIShortNameLen]]; }; GetTextInfo: PROC[sH: SchemaHandle, m: LoganBerry.Entry] RETURNS[textStart, textLen, formatLen: INT] = { rel: Relship = m; textStart ¬ LoganBerryEntry.V2I[LoganBerryEntry.GetAttr[rel, sH.mMIEntryStart]] + LoganBerryEntry.V2I[LoganBerryEntry.GetAttr[rel, sH.mMITextOffset]]; textLen¬ LoganBerryEntry.V2I[LoganBerryEntry.GetAttr[rel, sH.mMITextLen]]; formatLen ¬ LoganBerryEntry.V2I[LoganBerryEntry.GetAttr[rel, sH.mMIFormatLen]]; }; SetAllMsgInfo: PROC[opsH: WalnutOpsHandle, m: ROPE, mle: MsgLogEntry, herald: ROPE, shortNameLen: INT, date: BasicTime.GMT, sender: Address] = { sH: WalnutSchema.SchemaHandle = opsH.schemaHandle; isInReplyTo: BOOL ¬ FALSE; sub: ROPE = mle.subject; subject: Subject; subName: ROPE; afterRe: INT ¬ 0; DO IF sub.Find["Re: ", afterRe, FALSE] # afterRe THEN EXIT; afterRe ¬ afterRe + 4; isInReplyTo ¬ TRUE; ENDLOOP; subName ¬ sub.Substr[start: afterRe, len: 20]; IF subName.Length[] = 0 THEN subName ¬ "No Subject Field"; subject ¬ subName; BEGIN init: LoganBerry.Entry = LIST[ [$Key, Rope.Concat[sH.mMsgInfo, m]], [sH.mMIOf, m], [sH.mMIHerald, herald], [sH.mMIShortNameLen, LoganBerryEntry.I2V[shortNameLen]], [sH.mMIEntryStart, LoganBerryEntry.I2V[mle.entryStart]], [sH.mMITextOffset, LoganBerryEntry.I2V[mle.textOffset]], [sH.mMITextLen, LoganBerryEntry.I2V[mle.textLen]], [sH.mMIFormatLen, LoganBerryEntry.I2V[mle.formatLen]], [sH.mMIDate, LoganBerryEntry.T2V[date]], [sH.mMISubject, subject], [sH.mMISubjectText, Rope.Substr[base: mle.subject, len: 99]], [sH.mMIIsInReplyTo, LoganBerryEntry.B2V[isInReplyTo]], [sH.mMISender, sender] ]; LoganBerry.WriteEntry[db: opsH.db, entry: init]; END; }; SetMsgDisplayInfo: PROC[opsH: WalnutOpsHandle, m: ROPE, hasBeenRead: BOOL, tocHeadEntry: ROPE] = { sH: WalnutSchema.SchemaHandle = opsH.schemaHandle; init: LoganBerry.Entry = LIST[ [$Key, Rope.Concat[sH.mDisplayInfo, m]], [sH.mDIOf, m], [sH.mDITOCHeadEntry, tocHeadEntry], [sH.mDIHasBeenRead, LoganBerryEntry.B2V[hasBeenRead]] ]; LoganBerry.WriteEntry[db: opsH.db, entry: init]; }; SetMsgInfo: PROC[opsH: WalnutOpsHandle, m: ROPE, date: GMT, show: BOOL] = { sH: WalnutSchema.SchemaHandle = opsH.schemaHandle; init: LoganBerry.Entry = LIST[ [$Key, Rope.Concat[sH.mInfo, m]], [sH.mInfoOf, m], [sH.mDateIs, LoganBerryEntry.T2V[date]], [sH.mShowIs, IF show THEN "NULL" ELSE opsH.schemaHandle.unacceptedEntity] ]; LoganBerry.WriteEntry[db: opsH.db, entry: init]; }; ParseForSender: PROC[sH: SchemaHandle, adr: ROPE] RETURNS[e: Address, defaultReg: ROPE] = { thisName: ROPE; thisName ¬ NameInWalnutFormat[adr]; defaultReg ¬ BreakName[thisName].reg; e ¬ thisName; }; BreakName: PROC[name: Rope.ROPE] RETURNS[sn, reg: Rope.ROPE] = { length: INT = name.Length[]; FOR i: INT DECREASING IN [0..length) DO IF name.Fetch[i] = '. THEN RETURN[ sn: name.Substr[start: 0, len: i], reg: name.Substr[start: i+1, len: length-(i+1)] ]; ENDLOOP; RETURN[sn: name, reg: NIL]; }; NameInWalnutFormat: PROC[nameIn: ROPE] RETURNS[ROPE] = { this: ROPE ¬ JustTheName[nameIn]; <> <> <> <