Is there a way to detect the last written char?
Hello.
I'm using device redirection to intercept incoming writes and need to figure a smart way to detect when to CLRF in order to prevent misleading outputs.
So basically, if the intercepted write ends with !, I need to know that and prevent my routine from writing ! as well.
Here's the source code for it:
ClassMethod Call( sourceControl As %Studio.Extension.Base, hookName As %String = "", parameters... As %String) As %Status [ Internal, ProcedureBlock = 0 ] { new sc, content, implementer, alreadyRedirected, isNewLineTerminator, currentMnemonic set sc = $$$OK set implementer = ##class(Port.Configuration).GetExtendedHooksImplementer() set alreadyRedirected = ##class(%Device).ReDirectIO() set isNewLineTerminator = 0 if '##class(%Dictionary.CompiledMethod).%ExistsId(implementer_"||"_hookName) return sc set content = ##class(%Stream.GlobalBinary).%New() if implementer '= "" { write "Port: "_$$$FormatMsg("Port Log Messages", $$$RunningCustomHook, hookName, implementer) try { set currentMnemonic = "^"_##class(%Device).GetMnemonicRoutine() use $io::("^"_$zname) do ##class(%Device).ReDirectIO(1) write ! // Should be smart! set sc = $classmethod(implementer, hookName, sourceControl, parameters...) } catch ex { set content = "" set sc = ex.AsStatus() } } if alreadyRedirected { do ##class(%Device).ReDirectIO(1) use $io::(currentMnemonic) } if $isobject(content) { do content.OutputToDevice() do content.MoveTo(content.Size) set isNewLineTerminator = (content.Read(1) = $char(10)) } if 'isNewLineTerminator write ! if $$$ISOK(sc) { write "Port: "_$$$FormatMsg("Port Log Messages", $$$HookReturnedOK, hookName) } else { write "Port: "_$$$FormatMsg("Port Log Messages", $$$HookReturnedError, hookName, $System.Status.GetOneStatusText(sc)) } return $$$OK rchr(c) quit rstr(sz,to) quit wchr(s) do output($char(s)) quit wff() do output($char(12)) quit wnl() do output($char(13,10)_$$$FormatText("Port (%1): ", hookName)) quit // Should be smart as well: routines ending with write ! will create an empty feedback! wstr(s) do output(s) quit wtab(s) do output($char(9)) quit output(s) do content.Write(s) quit }
I bolded the part of what I tried to do to resolve this situation but didn't worked.
Thanks in advance.
1)
%Stream.GlobalBinary has a warning:
Note that on a unicode Cache although you can write unicode data into this class and read it back out this is not a supported use of this class and the Size property would be incorrect in this case as it would be the size in characters rather than the size in bytes
2)
do content.OutputToDevice()
now your content is out and you are positioned at end
do content.MoveTo(content.Size)
now you re-read content until LastCharacter-1, whatever total size might be
set isNewLineTerminator = (content.Read(1) = $char(10))
reading the assumed last character
3)
you might be better off to use content.ReadLine(,.sc,.eol) and check eol for the termination status
Updated to use %Stream.GlobalCharacter instead, thank you.
Using eol also didn't resolved I guess. Here's the feedback:
Port (OnAfterSave):
Port (OnAfterSave): First lineSecond line
Port (OnAfterSave): Third line
Port (OnAfterSave):
Port: OnAfterSave returned without errors.
sourceControl As %Studio.Extension.Base = "",
InternalName,
Location,
Object) As %Status
{
// ! skips the first line leaving it empty, actual message is pushed down to the second line.
// should not even print it.
write !, "First line"
// First line is here, printed along with the second.
write "Second line", !
// Third is still third line. But ! writes another empty line.
write "Third line", !
return $$$OK
}
As you suggested, I changed it to use eol:
do content.OutputToDevice()
do content.MoveTo(content.Size - 1)
do content.ReadLine(, sc, .isEOL)
}
if 'isEOL write !
congratualtions !
just a guess:
with do content.MoveTo(content.Size - 1) you are probably just between CR and LF
Could you try do content.MoveTo(content.Size - 3) just to make sure you a e before CRLF
I'm not sure how EOL is triggered
eventually also check returned success code
%Status always returned $$$OK and putting -3 instead -1 provided me the same feedback. The last line is still empty.
wnl() do output($char(13,10)_$$$FormatText("Port (%1): ", hookName)) quit
Maybe it's something regarding the redirection routines. I can't really see that:
As correct, since wnl doesn't even take a parameter where I could decide what to do.
I don't see a call for wnl()
but ancient coding practices would suggest its: writenewline just that
and that's what it does without any other content.
though the remark is rater direct if interpreting feedback := line
Indeed, those redirection routines aren't called anywhere. But they're redefined extensively around the Caché Class API.
As you said it's cryptic and powerful enough to dictate the buffer workflow even though you don't use it explicitly.
From my experiments, what I notice is:
wstr handles every write
wnl handles every !
wchr handles every single char
wtab handles tab usage
wff handles every form feed
As used here.
if you change wnl() ...
to
wnl() quit
you just disable it to see if it plays a role at all
indeed this is surprising.
but is this really: do content.ReadLine(,,isEOL)
it should be a pass by reference to receive something back do content.ReadLine(,,.isEOL)
with the <dot> in front of the variable
Yeah, I had fixed it but didn't posted the update and then started working on output, sorry.
to me this looks like your content had an extra $C(13,10) at the end.
It could help to have the full content at hands.
eg:
before content.OutputToDevice()
set ck=content.Read()
set ^ck($i(^ck))=ck
do content.Rewind()
I expect zwrite ^ck will show more than 3 lines
That would indicate that the source of trouble is on the input side.
Some closing action ?
do content.Rewind()
while 'content.AtEnd { set ^ck($i(i)) = content.ReadLine() }
do content.OutputToDevice()
do content.Rewind()
do content.MoveTo(content.Size -3)
do content.ReadLine(,,isEOL)
}
^ck(2)="Port (OnAfterSave): First lineSecond line" <-- Though First line is actually pushed down and merged with "Second line"
^ck(3)="Port (OnAfterSave): Third line" <-- Third works fine, because second finishes with !
^ck(4)="Port (OnAfterSave): " <-- write "Third line", ! creates a blank line though my prefix is displaying.
I'm starting to think there's no way to prevent all situations.
I finally got it to work as I desired. Here's the source code, take a look on output routine:
This will:
Limit usage of ! to one per write.
Prevent initial write ! as I don't want to skip any line on the beginning.
Display compiler messages correctly.
Prevent from writing new lines for empty buffers.
Include portutils
Class Port.SourceControl.ExtendedHooks [ Abstract ]
{
ClassMethod Call(
sourceControl As %Studio.Extension.Base,
hookName As %String = "",
parameters... As %String) As %Status [ Internal, ProcedureBlock = 0 ]
{
new sc, content, implementer, alreadyRedirected, currentMnemonic, childSC, expectingContent, firstLine
set sc = $$$OK
set childSC = $$$OK
set implementer = ##class(Port.Configuration).GetExtendedHooksImplementer()
set alreadyRedirected = ##class(%Device).ReDirectIO()
set expectingContent = 0
set firstLine = 0
if '##class(%Dictionary.CompiledMethod).%ExistsId(implementer_"||"_hookName) return sc
set content = ##class(%Stream.GlobalBinary).%New()
if implementer '= "" {
write !, "Port: "_$$$FormatMsg("Port Log Messages", $$$RunningCustomHook, hookName, implementer)
try {
set currentMnemonic = "^"_##class(%Device).GetMnemonicRoutine()
use $io::("^"_$zname)
do ##class(%Device).ReDirectIO(1)
set sc = $classmethod(implementer, hookName, sourceControl, parameters...)
} catch ex {
set content = ""
set sc = ex.AsStatus()
}
}
if alreadyRedirected {
do ##class(%Device).ReDirectIO(1)
use $io::(currentMnemonic)
}
if $isobject(content) {
do content.OutputToDevice()
}
write !
if $$$ISOK(sc) {
write "Port: "_$$$FormatMsg("Port Log Messages", $$$HookReturnedOK, hookName)
} else {
set errorText = $System.Status.GetOneStatusText(sc)
write "Port: "_$$$FormatMsg("Port Log Messages", $$$HookReturnedError, hookName, errorText)
set childSC = sc
set sc = $$$PERROR($$$FailedWhileRunningExtendedHook, hookName)
set sc = $$$EMBEDSC(sc, childSC)
}
return sc
rchr(c)
quit
rstr(sz,to)
quit
wchr(s)
do output($char(s))
quit
wff()
do output($char(12))
quit
wnl()
if firstLine = 0 set firstLine = 1
else set firstLine = -1
do output($char(13,10))
quit
wstr(s)
do output(s)
quit
wtab(s)
do output($char(9))
quit
output(s)
// Skips writing the first !, we leave it to our write.
if firstLine = 1 quit
// Remaining writes ! are always a standalone buffer so we can check it's equality.
if s = $c(13,10) {
// However we can only write if the the next buffer has indeed some content.
// So we defer it to the next call where we can actually assert it.
set expectingContent = 1
// This catches writes with embedded CRLF (like the compiler ones).
} elseif $extract(s, 1, 2) = $c(13,10) {
set expectingContent = 1
do output($replace(s, $c(13,10), ""))
set expectingContent = 0
quit
} elseif $length(s) > 0 {
// After deferring it, we can finally write a CRLF and the content, as long as it's not empty.
if expectingContent = 1 {
set expectingContent = 0
do content.WriteLine()
do content.Write($$$FormatText("Port (%1): ", hookName))
}
// Writes without ! must be written on the same line.
do content.Write(s)
}
quit
}
}