Rocksolid Light

Welcome to Rocksolid Light

mail  files  register  newsreader  groups  login

Message-ID:  

Neutrinos have bad breadth.


devel / comp.lang.tcl / Re: Return value from channel to be used as argument

SubjectAuthor
* Return value from channel to be used as argumentLuis Mendes
+* Re: Return value from channel to be used as argumentRich
|+- Re: Return value from channel to be used as argumentRalf Fassel
|`* Re: Return value from channel to be used as argumentLuis Mendes
| `* Re: Return value from channel to be used as argumentRich
|  `* Re: Return value from channel to be used as argumentLuis Mendes
|   `* Re: Return value from channel to be used as argumentRich
|    `- Re: Return value from channel to be used as argumentLuis Mendes
`* Re: Return value from channel to be used as argumentet99
 `* Re: Return value from channel to be used as argumentLuis Mendes
  `* Re: Return value from channel to be used as argumentet99
   `- Re: Return value from channel to be used as argumentLuis Mendes

1
Return value from channel to be used as argument

<661d8604$0$705$14726298@news.sunsite.dk>

  copy mid

https://news.novabbs.org/devel/article-flat.php?id=13654&group=comp.lang.tcl#13654

  copy link   Newsgroups: comp.lang.tcl
Path: i2pn2.org!i2pn.org!usenet.goja.nl.eu.org!dotsrc.org!filter.dotsrc.org!news.dotsrc.org!not-for-mail
From: luisXXXlupeXXX@gmail.com (Luis Mendes)
Subject: Return value from channel to be used as argument
Newsgroups: comp.lang.tcl
MIME-Version: 1.0
User-Agent: Pan/0.154 (Izium; 517acf4)
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Date: 15 Apr 2024 19:54:44 GMT
Lines: 64
Message-ID: <661d8604$0$705$14726298@news.sunsite.dk>
Organization: SunSITE.dk - Supporting Open source
NNTP-Posting-Host: 9c4204e0.news.sunsite.dk
X-Trace: 1713210884 news.sunsite.dk 705 luislupe@gmail.com/149.90.63.252:59348
X-Complaints-To: staff@sunsite.dk
 by: Luis Mendes - Mon, 15 Apr 2024 19:54 UTC

Hi,

I don't have the code to show up as it's in a corporate environment, but
I'll explain where is my difficulty.

I've already built a script to parse text output from a command.
processLine does what I need, but I'd like to have it assyncrhonouly:

proc processLine {line inside_block} {
if {[string match "SOMETEXT *" $line] && $inside_block == 0} {
# block name or header
set inside_block 1
set block_name .....
dict set parsed $block_name (simplified)
return $inside_block
}
if {$inside_block && blank line..} {
# it's a block separator
set inside_block 0
}
if {$inside_block} {
# lines after the block header and before the next blank line
# do some parsing and fill in parsed dict
}
}

proc parseLine {chan inside_block} {
set status [catch {chan gets $chan line} nchars]
if {$status == 0 && $nchars >= 0} {
set inside_block [processLine $line $inside_block]
return
}
if error or EOF
return
return $inside_block
}

And the main:
set inside_block 0
set chan [open |[list {*}shell_command] r]

chan event $chan readable [list parseLine $chan $inside_block]

vwait...
show dict parsed

So, in the first version I built, the synchronous one, the 'inside_block'
value is passed back and forth so that 'processLine' know where does the
context of the line.

But how can I do this when using channels for an asynchronous version?
It would be something like:
when 'chan event $chan readable ...' detects a new line arriving,
it should read the result of 'parseLine $chan $inside_block', so that for
the next line the new value of inside_block could be passed as an argument
to 'parseLine'.

I'd appreciate some help on this.
Thank you,

Luís Mendes

Re: Return value from channel to be used as argument

<uvk8mn$h4l4$1@dont-email.me>

  copy mid

https://news.novabbs.org/devel/article-flat.php?id=13655&group=comp.lang.tcl#13655

  copy link   Newsgroups: comp.lang.tcl
Path: i2pn2.org!i2pn.org!eternal-september.org!feeder3.eternal-september.org!news.eternal-september.org!.POSTED!not-for-mail
From: rich@example.invalid (Rich)
Newsgroups: comp.lang.tcl
Subject: Re: Return value from channel to be used as argument
Date: Mon, 15 Apr 2024 22:11:35 -0000 (UTC)
Organization: A noiseless patient Spider
Lines: 96
Message-ID: <uvk8mn$h4l4$1@dont-email.me>
References: <661d8604$0$705$14726298@news.sunsite.dk>
Injection-Date: Tue, 16 Apr 2024 00:11:36 +0200 (CEST)
Injection-Info: dont-email.me; posting-host="438ac2c61fe534e5390aa9379719fb7a";
logging-data="561828"; mail-complaints-to="abuse@eternal-september.org"; posting-account="U2FsdGVkX19NB/gzkdcMja33KLHss9Fd"
User-Agent: tin/2.6.1-20211226 ("Convalmore") (Linux/5.15.139 (x86_64))
Cancel-Lock: sha1:HGtEYfT4nbojEADWCgbBPlSEW30=
 by: Rich - Mon, 15 Apr 2024 22:11 UTC

Luis Mendes <luisXXXlupeXXX@gmail.com> wrote:
> I don't have the code to show up as it's in a corporate environment, but
> I'll explain where is my difficulty.
>
> ... [see prior post for code]
>
> So, in the first version I built, the synchronous one, the
> 'inside_block' value is passed back and forth so that 'processLine'
> know where does the context of the line.
>
> But how can I do this when using channels for an asynchronous version?
> It would be something like:
> when 'chan event $chan readable ...' detects a new line arriving,
> it should read the result of 'parseLine $chan $inside_block', so that for
> the next line the new value of inside_block could be passed as an argument
> to 'parseLine'.
>
> I'd appreciate some help on this.
> Thank you,

You have several options.

1) make 'inside_block' a global:

proc processLine {line} {
if {[string match "SOMETEXT *" $line] && $inside_block == 0} {
# block name or header
set ::inside_block 1
set block_name .....
dict set parsed $block_name (simplified)
}
if {$::inside_block && blank line..} {
# it's a block separator
set ::inside_block 0
}
if {$::inside_block} {
# lines after the block header and before the next blank line
# do some parsing and fill in parsed dict
}
}

But, then, you can only have one instance of this parse going on at one
time.

2) move "processLine" into a namespace, and use a namespace variable:

namespace eval SomeName {
variable inside_block 0

proc processLine {line} {
if {[string match "SOMETEXT *" $line] && $inside_block == 0} {
# block name or header
set inside_block 1
set block_name .....
dict set parsed $block_name (simplified)
}
if {$inside_block && blank line..} {
# it's a block separator
set inside_block 0
}
if {$inside_block} {
# lines after the block header and before the next blank line
# do some parsing and fill in parsed dict
}
}
}

And then setup the callback to call SomeName::processLine

With this method you can have more than one parse at a time running,
provided you create differently named namespaces

3) Make processLine an object and use an object variable (code omitted)

With this method, you can create "processLine" objects as needed to
attach to the callbacks, and they each get their own unique variable
namespace automatically. If you do plan on running plural parses at
the same time then this is your better option, as it frees you from
needing to work out how to handle making custom named namespaces to
have plural parses going on.

If you only ever plan to parse one thing at a time, either a global or
a namespace work. Which is 'better' is somewhat dependent upon what
kind of 'code review' you might have to endure.

See https://www.magicsplat.com/articles/oo.html for a good description
of objects.

4) You can redefine the callback as you go, so you 'could' pass the
value in via the callback by redefining the callback to contain the
value that should be present for the next call to processLine.

And there are probably a few more options that have not come to mind
yet.

Re: Return value from channel to be used as argument

<ygao7a9it3n.fsf@panther.akutech-local.de>

  copy mid

https://news.novabbs.org/devel/article-flat.php?id=13659&group=comp.lang.tcl#13659

  copy link   Newsgroups: comp.lang.tcl
Path: i2pn2.org!i2pn.org!news.swapon.de!fu-berlin.de!uni-berlin.de!individual.net!not-for-mail
From: ralfixx@gmx.de (Ralf Fassel)
Newsgroups: comp.lang.tcl
Subject: Re: Return value from channel to be used as argument
Date: Tue, 16 Apr 2024 10:56:12 +0200
Lines: 22
Message-ID: <ygao7a9it3n.fsf@panther.akutech-local.de>
References: <661d8604$0$705$14726298@news.sunsite.dk>
<uvk8mn$h4l4$1@dont-email.me>
Mime-Version: 1.0
Content-Type: text/plain
X-Trace: individual.net YZeA9Sg0OZnCxW+ksMDj/wEkoWXdd79QMtiXzEVHXpD00m0iU=
Cancel-Lock: sha1:tfXUOSkVZBNndNsl35V5NfCr5ko= sha1:Hh2Tbqz9mHf49iqSiNg7zXjmj+U= sha256:64tYOspKOWuPL3OY7/v0hv+jjMqGyycKj37sGtn2Zq0=
User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/27.2 (gnu/linux)
 by: Ralf Fassel - Tue, 16 Apr 2024 08:56 UTC

* Rich <rich@example.invalid>
| Luis Mendes <luisXXXlupeXXX@gmail.com> wrote:
| > It would be something like:
| > when 'chan event $chan readable ...' detects a new line arriving,
| > it should read the result of 'parseLine $chan $inside_block', so that for
| > the next line the new value of inside_block could be passed as an argument
| > to 'parseLine'.
| >
| > I'd appreciate some help on this.
| > Thank you,
>
| You have several options.
--<snip-snip>--
| And there are probably a few more options that have not come to mind
| yet.

Could coroutines come into play here? I haven't played with them yet,
but stored them as "keep context and continue from there after suspend",
so it sounds quite what the OP needs. Might be too complex for the task
at hand, though...

R'

Re: Return value from channel to be used as argument

<661ebd68$0$713$14726298@news.sunsite.dk>

  copy mid

https://news.novabbs.org/devel/article-flat.php?id=13667&group=comp.lang.tcl#13667

  copy link   Newsgroups: comp.lang.tcl
Path: i2pn2.org!i2pn.org!usenet.goja.nl.eu.org!dotsrc.org!filter.dotsrc.org!news.dotsrc.org!not-for-mail
From: luisXXXlupeXXX@gmail.com (Luis Mendes)
Subject: Re: Return value from channel to be used as argument
Newsgroups: comp.lang.tcl
References: <661d8604$0$705$14726298@news.sunsite.dk>
<uvk8mn$h4l4$1@dont-email.me>
MIME-Version: 1.0
User-Agent: Pan/0.154 (Izium; 517acf4)
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Date: 16 Apr 2024 18:03:20 GMT
Lines: 85
Message-ID: <661ebd68$0$713$14726298@news.sunsite.dk>
Organization: SunSITE.dk - Supporting Open source
NNTP-Posting-Host: e9861be6.news.sunsite.dk
X-Trace: 1713290600 news.sunsite.dk 713 luislupe@gmail.com/149.90.63.252:47260
X-Complaints-To: staff@sunsite.dk
 by: Luis Mendes - Tue, 16 Apr 2024 18:03 UTC

Hi Rich,

Thank you for your answer.
Please, see below.

On Mon, 15 Apr 2024 22:11:35 -0000 (UTC), Rich wrote:
> You have several options.
>
> 1) make 'inside_block' a global:
> But, then, you can only have one instance of this parse going on at one
> time.
I want to have several instances running at the same time from several
channels, so no global variable.

> 2) move "processLine" into a namespace, and use a namespace variable:
>
> namespace eval SomeName {
> variable inside_block 0
>
> proc processLine {line} {
> if {[string match "SOMETEXT *" $line] && $inside_block == 0} {
> # block name or header set inside_block 1 set block_name
> .....
> dict set parsed $block_name (simplified)
> }
> if {$inside_block && blank line..} {
> # it's a block separator set inside_block 0
> }
> if {$inside_block} {
> # lines after the block header and before the next blank
> line # do some parsing and fill in parsed dict
> }
> }
> }
>
> And then setup the callback to call SomeName::processLine
>
> With this method you can have more than one parse at a time running,
> provided you create differently named namespaces
So, would it be a namespace created dynamically?
Could you please share how a small example?

> 3) Make processLine an object and use an object variable (code omitted)
>
> With this method, you can create "processLine" objects as needed to
> attach to the callbacks, and they each get their own unique variable
> namespace automatically. If you do plan on running plural parses at the
> same time then this is your better option, as it frees you from needing
> to work out how to handle making custom named namespaces to have plural
> parses going on.
>
> If you only ever plan to parse one thing at a time, either a global or a
> namespace work. Which is 'better' is somewhat dependent upon what kind
> of 'code review' you might have to endure.
>
> See https://www.magicsplat.com/articles/oo.html for a good description
> of objects.
Never used OO in Tcl, not a fan of OO, but I'm trying to build something
around it.
The two main variable inside_block and chan could be set for the instance
to be visible all over the class and then I'd create instances of the
object like so?

oo::class create Parse {
variable inside_block chan
method parseLine {} {...}
method processLine {} {...}
}

For the main part of the script:
foreach id $exec_ids {
set inst${id} [Parse new]
}

>
> 4) You can redefine the callback as you go, so you 'could' pass the
> value in via the callback by redefining the callback to contain the
> value that should be present for the next call to processLine.
What should I change in this line?
chan event $chan readable [list parseLine $chan $inside_block]

Re: Return value from channel to be used as argument

<uvmfum$137qd$1@dont-email.me>

  copy mid

https://news.novabbs.org/devel/article-flat.php?id=13668&group=comp.lang.tcl#13668

  copy link   Newsgroups: comp.lang.tcl
Path: i2pn2.org!i2pn.org!eternal-september.org!feeder3.eternal-september.org!news.eternal-september.org!.POSTED!not-for-mail
From: rich@example.invalid (Rich)
Newsgroups: comp.lang.tcl
Subject: Re: Return value from channel to be used as argument
Date: Tue, 16 Apr 2024 18:27:34 -0000 (UTC)
Organization: A noiseless patient Spider
Lines: 121
Message-ID: <uvmfum$137qd$1@dont-email.me>
References: <661d8604$0$705$14726298@news.sunsite.dk> <uvk8mn$h4l4$1@dont-email.me> <661ebd68$0$713$14726298@news.sunsite.dk>
Injection-Date: Tue, 16 Apr 2024 20:27:35 +0200 (CEST)
Injection-Info: dont-email.me; posting-host="438ac2c61fe534e5390aa9379719fb7a";
logging-data="1154893"; mail-complaints-to="abuse@eternal-september.org"; posting-account="U2FsdGVkX189nUoSsGqjQLYN+R8K2RsJ"
User-Agent: tin/2.6.1-20211226 ("Convalmore") (Linux/5.15.139 (x86_64))
Cancel-Lock: sha1:yhBC0LtKWVN/zuEAlYc4emgkYKc=
 by: Rich - Tue, 16 Apr 2024 18:27 UTC

Luis Mendes <luisXXXlupeXXX@gmail.com> wrote:
> Hi Rich,
>
> Thank you for your answer.
> Please, see below.
>
> On Mon, 15 Apr 2024 22:11:35 -0000 (UTC), Rich wrote:
>> You have several options.
>>
>> 1) make 'inside_block' a global:
>> But, then, you can only have one instance of this parse going on at one
>> time.
> I want to have several instances running at the same time from several
> channels, so no global variable.

Then, obviously, this is not a reasonable solution.

>> 2) move "processLine" into a namespace, and use a namespace variable:
>>
>> namespace eval SomeName {
>> variable inside_block 0
>>
>> proc processLine {line} {
>> if {[string match "SOMETEXT *" $line] && $inside_block == 0} {
>> # block name or header set inside_block 1 set block_name
>> .....
>> dict set parsed $block_name (simplified)
>> }
>> if {$inside_block && blank line..} {
>> # it's a block separator set inside_block 0
>> }
>> if {$inside_block} {
>> # lines after the block header and before the next blank
>> line # do some parsing and fill in parsed dict
>> }
>> }
>> }
>>
>> And then setup the callback to call SomeName::processLine
>>
>> With this method you can have more than one parse at a time running,
>> provided you create differently named namespaces
> So, would it be a namespace created dynamically?
> Could you please share how a small example?

Yes, you'd have to do dynamic namespace creation. Which 'could' be
something like:

set uid 0

namespace eval pl-[incr uid] {
...
proc ... {} {...}
proc ... {} {...}
}

But, if you use objects, you get all of the above handled for your
automatically as part of the 'new' or 'create' call to creating class.
Which dramatically simplifies the effort you have to put in (you can
focus on your actual project, not on building a quasi-object layer out
of namespaces).

>> 3) Make processLine an object and use an object variable (code omitted)
>>
>> See https://www.magicsplat.com/articles/oo.html for a good description
>> of objects.

> Never used OO in Tcl, not a fan of OO, but I'm trying to build
> something around it.

Once you start to use it some, you'll be surprised as all the places
where you created a "partial object layer" to avoid global variables,
but didn't realize you were building part of an 'object' system at the
time.

> The two main variable inside_block and chan could be set for the
> instance to be visible all over the class and then I'd create
> instances of the object like so?
>
> oo::class create Parse {
> variable inside_block chan
> method parseLine {} {...}
> method processLine {} {...}
> }

Yes, that's the general template of what you'd want. Note you can also
have a 'constructor' method that can do extra work to 'create' the
object when you call 'new' or 'create' and you can have a 'destructor'
method that can cleanup (such as by being sure that 'chan' is closed
and the like).

> For the main part of the script:
> foreach id $exec_ids {
> set inst${id} [Parse new]
> }

Yes, and each 'new' call gives you an object with its own private
"inside_block" and 'chan' variabes, so you can have plural parses going
simultaneously with no conflict (at least no conflict at this level of
your project). And if you setup a 'destructor' then you can simply
call 'destroy' on the object, and have everything cleaned up ('chan'
channel closed, any memory allocated that does not get cleaned up by
Tcl's reference counting freed, etc.).

>> 4) You can redefine the callback as you go, so you 'could' pass the
>> value in via the callback by redefining the callback to contain the
>> value that should be present for the next call to processLine.
> What should I change in this line?
> chan event $chan readable [list parseLine $chan $inside_block]

You'd put that into the "process proc" such that just before you
existed 'process' you'd call that chan event, to attach the new value
of "$inside_block" to the handler.

Using a minimum amount of 'objects' (one class, for creating each
'parse' object and your parse procs as methods in those objects) is
likely the least complex of the various possibilties.

Then just attach the object to the event handler for each channel to
call the proper object method when the channel is readable.

Re: Return value from channel to be used as argument

<66200436$0$716$14726298@news.sunsite.dk>

  copy mid

https://news.novabbs.org/devel/article-flat.php?id=13673&group=comp.lang.tcl#13673

  copy link   Newsgroups: comp.lang.tcl
Path: i2pn2.org!i2pn.org!weretis.net!feeder8.news.weretis.net!usenet.goja.nl.eu.org!dotsrc.org!filter.dotsrc.org!news.dotsrc.org!not-for-mail
From: luisXXXlupeXXX@gmail.com (Luis Mendes)
Subject: Re: Return value from channel to be used as argument
Newsgroups: comp.lang.tcl
References: <661d8604$0$705$14726298@news.sunsite.dk>
<uvk8mn$h4l4$1@dont-email.me> <661ebd68$0$713$14726298@news.sunsite.dk>
<uvmfum$137qd$1@dont-email.me>
MIME-Version: 1.0
User-Agent: Pan/0.154 (Izium; 517acf4)
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Date: 17 Apr 2024 17:17:42 GMT
Lines: 131
Message-ID: <66200436$0$716$14726298@news.sunsite.dk>
Organization: SunSITE.dk - Supporting Open source
NNTP-Posting-Host: e58c54a2.news.sunsite.dk
X-Trace: 1713374262 news.sunsite.dk 716 luislupe@gmail.com/149.90.63.252:43756
X-Complaints-To: staff@sunsite.dk
 by: Luis Mendes - Wed, 17 Apr 2024 17:17 UTC

On Tue, 16 Apr 2024 18:27:34 -0000 (UTC), Rich wrote:
>>> 3) Make processLine an object and use an object variable (code
>>> omitted)
>>>
>>> See https://www.magicsplat.com/articles/oo.html for a good description
>>> of objects.
>
>> Never used OO in Tcl, not a fan of OO, but I'm trying to build
>> something around it.
>
> Once you start to use it some, you'll be surprised as all the places
> where you created a "partial object layer" to avoid global variables,
> but didn't realize you were building part of an 'object' system at the
> time.
>
>> The two main variable inside_block and chan could be set for the
>> instance to be visible all over the class and then I'd create instances
>> of the object like so?
>>
>> oo::class create Parse {
>> variable inside_block chan method parseLine {} {...} method
>> processLine {} {...}
>> }
>
> Yes, that's the general template of what you'd want. Note you can also
> have a 'constructor' method that can do extra work to 'create' the
> object when you call 'new' or 'create' and you can have a 'destructor'
> method that can cleanup (such as by being sure that 'chan' is closed and
> the like).
>
>> For the main part of the script:
>> foreach id $exec_ids {
>> set inst${id} [Parse new]
>> }
>
> Yes, and each 'new' call gives you an object with its own private
> "inside_block" and 'chan' variabes, so you can have plural parses going
> simultaneously with no conflict (at least no conflict at this level of
> your project). And if you setup a 'destructor' then you can simply call
> 'destroy' on the object, and have everything cleaned up ('chan' channel
> closed, any memory allocated that does not get cleaned up by Tcl's
> reference counting freed, etc.).

I've created a minimum example that is not working.

First, a simple example that works:
=== procedural ===
#!/usr/local/bin/tclsh

proc parseLine {chan_r} {
set status [catch {chan gets $chan_r line} nchars]
puts "status: $status line: $line nchars: $nchars"
if {$status == 0 && $nchars >= 0} {
puts "received: $line"
# processLine
return
}
if {$status || [chan eof $chan_r]} {
chan event $chan_r readable {}
set ::exit_flag 1
chan close $chan_r
return
}
puts "incomplete line"
}

set chan_r [open |[list ls] r]
chan configure $chan_r -blocking 0 -buffering line
chan event $chan_r readable [list parseLine $chan_r]

vwait ::exit_flag
===============

=== object oriented ===
#!/usr/local/bin/tclsh

oo::class create Parse {
variable chan_r
constructor {comm} {
set chan_r [open |[list $comm] r]
chan configure $chan_r -blocking 0 -buffering line
chan event $chan_r readable [my parseLine]
}
method parseLine {} {
set status [catch {chan gets $chan_r line} nchars]
puts "status: $status nchars: $nchars chan eof: [chan eof
$chan_r] chan blocked: [chan blocked $chan_r] line: $line"
if {$status == 0 && $nchars >= 0} {
puts "received: $line"
#my processLine $line
return
}
if {$status || [chan eof $chan_r]} {
puts ">> All received from test!"
chan event $chan_r readable {}
set ::exit_flag 1
chan close $chan_r
return
}
puts "incomplete line"
}
}

set exec_id 0
set run_${exec_id} [Parse new ls]

vwait ::exit_flag_simple
==========

This OO example returns one line if -blocking is set to 1, than is waiting
forever.
If -blocking 0, then prints nothing.

It seems that placing `chan event $chan_r readable [my parseLine]` in the
constructor method makes that the context for the chan event is lost
somehow.
How can this work?

Another question.
What I'm building is supposed to read a log from an Ansible process, so
lines are being sent to stdout for some minutes, one shortly after another.
I'd like the script to parse the log of several Ansible processes running
in parallel, thus the approach to 'set run_${exec_id} [Parse new ...]'.
Does the option '-blocking' make a difference in this context?

Thanks,

Luís

Re: Return value from channel to be used as argument

<uvp6iv$1oqv9$1@dont-email.me>

  copy mid

https://news.novabbs.org/devel/article-flat.php?id=13674&group=comp.lang.tcl#13674

  copy link   Newsgroups: comp.lang.tcl
Path: i2pn2.org!i2pn.org!eternal-september.org!feeder3.eternal-september.org!news.eternal-september.org!.POSTED!not-for-mail
From: rich@example.invalid (Rich)
Newsgroups: comp.lang.tcl
Subject: Re: Return value from channel to be used as argument
Date: Wed, 17 Apr 2024 19:06:08 -0000 (UTC)
Organization: A noiseless patient Spider
Lines: 121
Message-ID: <uvp6iv$1oqv9$1@dont-email.me>
References: <661d8604$0$705$14726298@news.sunsite.dk> <uvk8mn$h4l4$1@dont-email.me> <661ebd68$0$713$14726298@news.sunsite.dk> <uvmfum$137qd$1@dont-email.me> <66200436$0$716$14726298@news.sunsite.dk>
Injection-Date: Wed, 17 Apr 2024 21:06:08 +0200 (CEST)
Injection-Info: dont-email.me; posting-host="9688a292981eb2bbbeae528c76c0c6ff";
logging-data="1862633"; mail-complaints-to="abuse@eternal-september.org"; posting-account="U2FsdGVkX19TBxGfkER322PF+rUUFVSZ"
User-Agent: tin/2.6.1-20211226 ("Convalmore") (Linux/5.15.139 (x86_64))
Cancel-Lock: sha1:Jc3LNhACDhwVgr4du0kKVDYyjqc=
 by: Rich - Wed, 17 Apr 2024 19:06 UTC

Luis Mendes <luisXXXlupeXXX@gmail.com> wrote:
> On Tue, 16 Apr 2024 18:27:34 -0000 (UTC), Rich wrote:
>>>> 3) Make processLine an object and use an object variable (code
>>>> omitted)
>>>>
>>>> See https://www.magicsplat.com/articles/oo.html for a good description
>>>> of objects.
>>
>>> Never used OO in Tcl, not a fan of OO, but I'm trying to build
>>> something around it.
>>
>>>
>>> oo::class create Parse {
>>> variable inside_block chan method parseLine {} {...} method
>>> processLine {} {...}
>>> }
>>
>> Yes, that's the general template of what you'd want. Note you can also
>> have a 'constructor' method that can do extra work to 'create' the
>> object when you call 'new' or 'create' and you can have a 'destructor'
>> method that can cleanup (such as by being sure that 'chan' is closed and
>> the like).
>>
>>> For the main part of the script:
>>> foreach id $exec_ids {
>>> set inst${id} [Parse new]
>>> }
>>
>> Yes, and each 'new' call gives you an object with its own private
>> "inside_block" and 'chan' variabes, so you can have plural parses going
>> simultaneously with no conflict (at least no conflict at this level of
>> your project). And if you setup a 'destructor' then you can simply call
>> 'destroy' on the object, and have everything cleaned up ('chan' channel
>> closed, any memory allocated that does not get cleaned up by Tcl's
>> reference counting freed, etc.).
>
> I've created a minimum example that is not working.
>
>
> === object oriented ===
> #!/usr/local/bin/tclsh
>
> oo::class create Parse {
> variable chan_r
> constructor {comm} {
> set chan_r [open |[list $comm] r]
> chan configure $chan_r -blocking 0 -buffering line
> chan event $chan_r readable [my parseLine]
> }

If you do a quick test, you'll see what has gone wrong:

$ rlwrap tclsh
% oo::class create Parse {
constructor {} {
puts stderr "in constructor '[my parseLine]'"
}
method parseLine {} {
}
}
::Parse
% Parse new
in constructor ''
::oo::Obj12
%

The [my] call, in the constructor, returned nothing. Additionally,
[my] is defined only for use in methods, for calling other methods of
the same object. Event callbacks in Tcl register bits of script code
to execute later in time (i.e., not at the time of definition) and
almost allways execute the attached code in the global context (i.e.,
outside of your object).

You need to instead setup the event script as if you were calling the
object from the outside (which you in fact are doing) by calling the
desired method on the object's name):

$ rlwrap tclsh
% oo::class create Parse {
constructor {} {
puts stderr "in constructor '[self object]'"
}
}
::Parse
% Parse new
in constructor '::oo::Obj12'
::oo::Obj12
%

So your chan event needs to read:

chan event $chan_r readable [list [self object] parseLine]

And things should begin working (or at least the reason for not
receiving a callback will go away).

> It seems that placing `chan event $chan_r readable [my parseLine]` in the
> constructor method makes that the context for the chan event is lost
> somehow.

No, it never setup the event callback properly, because [my] in the
constructor returned an empty string.

> How can this work?

See above.

> Another question.
> What I'm building is supposed to read a log from an Ansible process,
> so lines are being sent to stdout for some minutes, one shortly after
> another. I'd like the script to parse the log of several Ansible
> processes running in parallel, thus the approach to 'set
> run_${exec_id} [Parse new ...]'. Does the option '-blocking' make a
> difference in this context?

Yes, if you want to use 'chan event' you have to set the channels to
non-blocking mode (-blocking 0). Otherwise you can't use 'chan event'
to have the channels handled in an event driven manner. And with
blocking, the first one where you try to gets or read and the channel
does not have enough data will block the entire process.

Re: Return value from channel to be used as argument

<uvq5ss$22hv3$1@dont-email.me>

  copy mid

https://news.novabbs.org/devel/article-flat.php?id=13675&group=comp.lang.tcl#13675

  copy link   Newsgroups: comp.lang.tcl
Path: i2pn2.org!i2pn.org!eternal-september.org!feeder3.eternal-september.org!news.eternal-september.org!.POSTED!not-for-mail
From: et99@rocketship1.me (et99)
Newsgroups: comp.lang.tcl
Subject: Re: Return value from channel to be used as argument
Date: Wed, 17 Apr 2024 21:00:28 -0700
Organization: A noiseless patient Spider
Lines: 88
Message-ID: <uvq5ss$22hv3$1@dont-email.me>
References: <661d8604$0$705$14726298@news.sunsite.dk>
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8; format=flowed
Content-Transfer-Encoding: 8bit
Injection-Date: Thu, 18 Apr 2024 06:00:29 +0200 (CEST)
Injection-Info: dont-email.me; posting-host="2d9012cc191c86b774364aef726c2488";
logging-data="2181091"; mail-complaints-to="abuse@eternal-september.org"; posting-account="U2FsdGVkX1+9H5NA49D3yjGA9KbZVTHK"
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:102.0) Gecko/20100101
Thunderbird/102.6.1
Cancel-Lock: sha1:2Z69LTN1uSnDi/J8fw7M3BTSbR0=
Content-Language: en-US
In-Reply-To: <661d8604$0$705$14726298@news.sunsite.dk>
 by: et99 - Thu, 18 Apr 2024 04:00 UTC

On 4/15/2024 12:54 PM, Luis Mendes wrote:
> Hi,
>
>
> I don't have the code to show up as it's in a corporate environment, but
> I'll explain where is my difficulty.
>
> I've already built a script to parse text output from a command.
> processLine does what I need, but I'd like to have it assyncrhonouly:
>
> proc processLine {line inside_block} {
> if {[string match "SOMETEXT *" $line] && $inside_block == 0} {
> # block name or header
> set inside_block 1
> set block_name .....
> dict set parsed $block_name (simplified)
> return $inside_block
> }
> if {$inside_block && blank line..} {
> # it's a block separator
> set inside_block 0
> }
> if {$inside_block} {
> # lines after the block header and before the next blank line
> # do some parsing and fill in parsed dict
> }
> }
>
> proc parseLine {chan inside_block} {
> set status [catch {chan gets $chan line} nchars]
> if {$status == 0 && $nchars >= 0} {
> set inside_block [processLine $line $inside_block]
> return
> }
> if error or EOF
> return
> return $inside_block
> }
>
> And the main:
> set inside_block 0
> set chan [open |[list {*}shell_command] r]
>
> chan event $chan readable [list parseLine $chan $inside_block]
>
> vwait...
> show dict parsed
>
>
> So, in the first version I built, the synchronous one, the 'inside_block'
> value is passed back and forth so that 'processLine' know where does the
> context of the line.
>
> But how can I do this when using channels for an asynchronous version?
> It would be something like:
> when 'chan event $chan readable ...' detects a new line arriving,
> it should read the result of 'parseLine $chan $inside_block', so that for
> the next line the new value of inside_block could be passed as an argument
> to 'parseLine'.
>
> I'd appreciate some help on this.
> Thank you,
>
>
> Luís Mendes

I've only quickly read your post, so I didn't see what you want to do with the parsing output.

Monitoring of N other processes by creating OO objects each involving event driven code is more like trying to simulate separate processes or threads all in a single threaded program.

Assuming you need to correlate the output of each "parser" thread in a single place (otherwise just exec-ing processes would be enough), then tcl threads is what you really want. And if you're not a fan of OO, then you might as well do a crash course on threads instead of tclOO :)

The advantage of threads is that each thread can do simple sequential coding:

open channels etc. store in global variables
loop
read-with-block, parse, report
endloop

(I'm assuming you have a modern tcl, say 8.6.6 or later)

Each new thread would also have it's own private set of global variables (sort of like variable in a class).

To collect the parse data from all the threads, (the report step) you would thread::send from the parser threads back to the main thread a call to some collection proc, with the collected data as the arguments. The main thread would just do a vwait for-exit. The collection proc would be called fifo from each parser thread, and provided you don't do any updates or vwaits, could also run "sequentially".

How you intend to know when to exit or fire off some additional monitoring threads is a bookkeeping job, left as an exercise for the reader.

Re: Return value from channel to be used as argument

<662148f9$0$713$14726298@news.sunsite.dk>

  copy mid

https://news.novabbs.org/devel/article-flat.php?id=13679&group=comp.lang.tcl#13679

  copy link   Newsgroups: comp.lang.tcl
Path: i2pn2.org!i2pn.org!usenet.goja.nl.eu.org!dotsrc.org!filter.dotsrc.org!news.dotsrc.org!not-for-mail
From: luisXXXlupeXXX@gmail.com (Luis Mendes)
Subject: Re: Return value from channel to be used as argument
Newsgroups: comp.lang.tcl
References: <661d8604$0$705$14726298@news.sunsite.dk>
<uvk8mn$h4l4$1@dont-email.me> <661ebd68$0$713$14726298@news.sunsite.dk>
<uvmfum$137qd$1@dont-email.me> <66200436$0$716$14726298@news.sunsite.dk>
<uvp6iv$1oqv9$1@dont-email.me>
MIME-Version: 1.0
User-Agent: Pan/0.154 (Izium; 517acf4)
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Date: 18 Apr 2024 16:23:21 GMT
Lines: 55
Message-ID: <662148f9$0$713$14726298@news.sunsite.dk>
Organization: SunSITE.dk - Supporting Open source
NNTP-Posting-Host: 1a9b0378.news.sunsite.dk
X-Trace: 1713457401 news.sunsite.dk 713 luislupe@gmail.com/149.90.63.252:54168
X-Complaints-To: staff@sunsite.dk
 by: Luis Mendes - Thu, 18 Apr 2024 16:23 UTC

On Wed, 17 Apr 2024 19:06:08 -0000 (UTC), Rich wrote:
>>
>> === object oriented ===
>> #!/usr/local/bin/tclsh
>>
>> oo::class create Parse {
>> variable chan_r constructor {comm} {
>> set chan_r [open |[list $comm] r]
>> chan configure $chan_r -blocking 0 -buffering line chan event
>> $chan_r readable [my parseLine]
>> }
>
> If you do a quick test, you'll see what has gone wrong:
>
> $ rlwrap tclsh % oo::class create Parse {
> constructor {} {
> puts stderr "in constructor '[my parseLine]'"
> }
> method parseLine {} {
> }
> }
> ::Parse % Parse new in constructor ''
> ::oo::Obj12 %
>
> The [my] call, in the constructor, returned nothing. Additionally, [my]
> is defined only for use in methods, for calling other methods of the
> same object. Event callbacks in Tcl register bits of script code to
> execute later in time (i.e., not at the time of definition) and almost
> allways execute the attached code in the global context (i.e., outside
> of your object).
>
> You need to instead setup the event script as if you were calling the
> object from the outside (which you in fact are doing) by calling the
> desired method on the object's name):
>
> $ rlwrap tclsh % oo::class create Parse {
> constructor {} {
> puts stderr "in constructor '[self object]'"
> }
> }
> ::Parse % Parse new in constructor '::oo::Obj12'
> ::oo::Obj12 %
>
> So your chan event needs to read:
>
> chan event $chan_r readable [list [self object] parseLine]
>
> And things should begin working (or at least the reason for not
> receiving a callback will go away).

Thank you very much Rich, this was a show stopper and with your help and
knowledge I could move forward.
All the best to you!

Luís

Re: Return value from channel to be used as argument

<662149c2$0$713$14726298@news.sunsite.dk>

  copy mid

https://news.novabbs.org/devel/article-flat.php?id=13680&group=comp.lang.tcl#13680

  copy link   Newsgroups: comp.lang.tcl
Path: i2pn2.org!i2pn.org!usenet.goja.nl.eu.org!dotsrc.org!filter.dotsrc.org!news.dotsrc.org!not-for-mail
From: luisXXXlupeXXX@gmail.com (Luis Mendes)
Subject: Re: Return value from channel to be used as argument
Newsgroups: comp.lang.tcl
References: <661d8604$0$705$14726298@news.sunsite.dk>
<uvq5ss$22hv3$1@dont-email.me>
MIME-Version: 1.0
User-Agent: Pan/0.154 (Izium; 517acf4)
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Date: 18 Apr 2024 16:26:42 GMT
Lines: 112
Message-ID: <662149c2$0$713$14726298@news.sunsite.dk>
Organization: SunSITE.dk - Supporting Open source
NNTP-Posting-Host: 1a9b0378.news.sunsite.dk
X-Trace: 1713457602 news.sunsite.dk 713 luislupe@gmail.com/149.90.63.252:54168
X-Complaints-To: staff@sunsite.dk
 by: Luis Mendes - Thu, 18 Apr 2024 16:26 UTC

On Wed, 17 Apr 2024 21:00:28 -0700, et99 wrote:

> On 4/15/2024 12:54 PM, Luis Mendes wrote:
>> Hi,
>>
>>
>> I don't have the code to show up as it's in a corporate environment,
>> but I'll explain where is my difficulty.
>>
>> I've already built a script to parse text output from a command.
>> processLine does what I need, but I'd like to have it assyncrhonouly:
>>
>> proc processLine {line inside_block} {
>> if {[string match "SOMETEXT *" $line] && $inside_block == 0} {
>> # block name or header set inside_block 1
>> set block_name .....
>> dict set parsed $block_name (simplified)
>> return $inside_block
>> }
>> if {$inside_block && blank line..} {
>> # it's a block separator
>> set inside_block 0
>> }
>> if {$inside_block} {
>> # lines after the block header and before the next blank line # do
>> some parsing and fill in parsed dict
>> }
>> }
>>
>> proc parseLine {chan inside_block} {
>> set status [catch {chan gets $chan line} nchars]
>> if {$status == 0 && $nchars >= 0} {
>> set inside_block [processLine $line $inside_block]
>> return
>> }
>> if error or EOF
>> return
>> return $inside_block
>> }
>>
>> And the main:
>> set inside_block 0 set chan [open |[list {*}shell_command] r]
>>
>> chan event $chan readable [list parseLine $chan $inside_block]
>>
>> vwait...
>> show dict parsed
>>
>>
>> So, in the first version I built, the synchronous one, the
>> 'inside_block' value is passed back and forth so that 'processLine'
>> know where does the context of the line.
>>
>> But how can I do this when using channels for an asynchronous version?
>> It would be something like:
>> when 'chan event $chan readable ...' detects a new line arriving,
>> it should read the result of 'parseLine $chan $inside_block', so that
>> for the next line the new value of inside_block could be passed as an
>> argument to 'parseLine'.
>>
>> I'd appreciate some help on this.
>> Thank you,
>>
>>
>> Luís Mendes
>
>
> I've only quickly read your post, so I didn't see what you want to do
> with the parsing output.
>
> Monitoring of N other processes by creating OO objects each involving
> event driven code is more like trying to simulate separate processes or
> threads all in a single threaded program.
>
> Assuming you need to correlate the output of each "parser" thread in a
> single place (otherwise just exec-ing processes would be enough), then
> tcl threads is what you really want. And if you're not a fan of OO, then
> you might as well do a crash course on threads instead of tclOO :)
>
> The advantage of threads is that each thread can do simple sequential
> coding:
>
> open channels etc. store in global variables loop
> read-with-block, parse, report
> endloop
>
> (I'm assuming you have a modern tcl, say 8.6.6 or later)
>
> Each new thread would also have it's own private set of global variables
> (sort of like variable in a class).
>
> To collect the parse data from all the threads, (the report step) you
> would thread::send from the parser threads back to the main thread a
> call to some collection proc, with the collected data as the arguments.
> The main thread would just do a vwait for-exit. The collection proc
> would be called fifo from each parser thread, and provided you don't do
> any updates or vwaits, could also run "sequentially".
>
> How you intend to know when to exit or fire off some additional
> monitoring threads is a bookkeeping job, left as an exercise for the
> reader.

Hi et99,

Yes, that's a good idea and threads I'd like to play with threads.
Some time ago, I tried a very simple example and it was segfaulting
although the code run sequentially was not. And didn't considered threads
for this example but could fit very well for solving the problem.

Thank you,

Luís

Re: Return value from channel to be used as argument

<uvsm9q$2n1jk$1@dont-email.me>

  copy mid

https://news.novabbs.org/devel/article-flat.php?id=13684&group=comp.lang.tcl#13684

  copy link   Newsgroups: comp.lang.tcl
Path: i2pn2.org!i2pn.org!eternal-september.org!feeder3.eternal-september.org!news.eternal-september.org!.POSTED!not-for-mail
From: et99@rocketship1.me (et99)
Newsgroups: comp.lang.tcl
Subject: Re: Return value from channel to be used as argument
Date: Thu, 18 Apr 2024 19:52:42 -0700
Organization: A noiseless patient Spider
Lines: 120
Message-ID: <uvsm9q$2n1jk$1@dont-email.me>
References: <661d8604$0$705$14726298@news.sunsite.dk>
<uvq5ss$22hv3$1@dont-email.me> <662149c2$0$713$14726298@news.sunsite.dk>
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8; format=flowed
Content-Transfer-Encoding: 8bit
Injection-Date: Fri, 19 Apr 2024 04:52:42 +0200 (CEST)
Injection-Info: dont-email.me; posting-host="f208de21314eb9b8043a99067ab5e1ec";
logging-data="2852468"; mail-complaints-to="abuse@eternal-september.org"; posting-account="U2FsdGVkX19R9ZKY6HIkybOFYjSxFxm7"
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:102.0) Gecko/20100101
Thunderbird/102.6.1
Cancel-Lock: sha1:rzwDeBgKCfAYMj9yWeOJslnfaQ8=
Content-Language: en-US
In-Reply-To: <662149c2$0$713$14726298@news.sunsite.dk>
 by: et99 - Fri, 19 Apr 2024 02:52 UTC

On 4/18/2024 9:26 AM, Luis Mendes wrote:
> On Wed, 17 Apr 2024 21:00:28 -0700, et99 wrote:
>
>> On 4/15/2024 12:54 PM, Luis Mendes wrote:
>>> Hi,
>>>
>>>
>>> I don't have the code to show up as it's in a corporate environment,
>>> but I'll explain where is my difficulty.
>>>
>>> I've already built a script to parse text output from a command.
>>> processLine does what I need, but I'd like to have it assyncrhonouly:
>>>
>>> proc processLine {line inside_block} {
>>> if {[string match "SOMETEXT *" $line] && $inside_block == 0} {
>>> # block name or header set inside_block 1
>>> set block_name .....
>>> dict set parsed $block_name (simplified)
>>> return $inside_block
>>> }
>>> if {$inside_block && blank line..} {
>>> # it's a block separator
>>> set inside_block 0
>>> }
>>> if {$inside_block} {
>>> # lines after the block header and before the next blank line # do
>>> some parsing and fill in parsed dict
>>> }
>>> }
>>>
>>> proc parseLine {chan inside_block} {
>>> set status [catch {chan gets $chan line} nchars]
>>> if {$status == 0 && $nchars >= 0} {
>>> set inside_block [processLine $line $inside_block]
>>> return
>>> }
>>> if error or EOF
>>> return
>>> return $inside_block
>>> }
>>>
>>> And the main:
>>> set inside_block 0 set chan [open |[list {*}shell_command] r]
>>>
>>> chan event $chan readable [list parseLine $chan $inside_block]
>>>
>>> vwait...
>>> show dict parsed
>>>
>>>
>>> So, in the first version I built, the synchronous one, the
>>> 'inside_block' value is passed back and forth so that 'processLine'
>>> know where does the context of the line.
>>>
>>> But how can I do this when using channels for an asynchronous version?
>>> It would be something like:
>>> when 'chan event $chan readable ...' detects a new line arriving,
>>> it should read the result of 'parseLine $chan $inside_block', so that
>>> for the next line the new value of inside_block could be passed as an
>>> argument to 'parseLine'.
>>>
>>> I'd appreciate some help on this.
>>> Thank you,
>>>
>>>
>>> Luís Mendes
>>
>>
>> I've only quickly read your post, so I didn't see what you want to do
>> with the parsing output.
>>
>> Monitoring of N other processes by creating OO objects each involving
>> event driven code is more like trying to simulate separate processes or
>> threads all in a single threaded program.
>>
>> Assuming you need to correlate the output of each "parser" thread in a
>> single place (otherwise just exec-ing processes would be enough), then
>> tcl threads is what you really want. And if you're not a fan of OO, then
>> you might as well do a crash course on threads instead of tclOO :)
>>
>> The advantage of threads is that each thread can do simple sequential
>> coding:
>>
>> open channels etc. store in global variables loop
>> read-with-block, parse, report
>> endloop
>>
>> (I'm assuming you have a modern tcl, say 8.6.6 or later)
>>
>> Each new thread would also have it's own private set of global variables
>> (sort of like variable in a class).
>>
>> To collect the parse data from all the threads, (the report step) you
>> would thread::send from the parser threads back to the main thread a
>> call to some collection proc, with the collected data as the arguments.
>> The main thread would just do a vwait for-exit. The collection proc
>> would be called fifo from each parser thread, and provided you don't do
>> any updates or vwaits, could also run "sequentially".
>>
>> How you intend to know when to exit or fire off some additional
>> monitoring threads is a bookkeeping job, left as an exercise for the
>> reader.
>
> Hi et99,
>
> Yes, that's a good idea and threads I'd like to play with threads.
> Some time ago, I tried a very simple example and it was segfaulting
> although the code run sequentially was not. And didn't considered threads
> for this example but could fit very well for solving the problem.
>
> Thank you,
>
> Luís

Hi Luis:

A while back I really didn't know much about the tcl threads package. But then I bought Ashok's book. I can't recommend it more highly. Whatever technique you decide on, threads, event driven, channels, tclOO, it's all in the book with great examples you can copy/paste right into your program (from the pdf version). As Rich pointed out, you can read the tclOO chapter for free on line.

-et

Re: Return value from channel to be used as argument

<66292784$0$709$14726298@news.sunsite.dk>

  copy mid

https://news.novabbs.org/devel/article-flat.php?id=13702&group=comp.lang.tcl#13702

  copy link   Newsgroups: comp.lang.tcl
Path: i2pn2.org!i2pn.org!usenet.goja.nl.eu.org!dotsrc.org!filter.dotsrc.org!news.dotsrc.org!not-for-mail
From: luisXXXlupeXXX@gmail.com (Luis Mendes)
Subject: Re: Return value from channel to be used as argument
Newsgroups: comp.lang.tcl
References: <661d8604$0$705$14726298@news.sunsite.dk>
<uvq5ss$22hv3$1@dont-email.me> <662149c2$0$713$14726298@news.sunsite.dk>
<uvsm9q$2n1jk$1@dont-email.me>
MIME-Version: 1.0
User-Agent: Pan/0.154 (Izium; 517acf4)
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Date: 24 Apr 2024 15:38:44 GMT
Lines: 20
Message-ID: <66292784$0$709$14726298@news.sunsite.dk>
Organization: SunSITE.dk - Supporting Open source
NNTP-Posting-Host: 42f076fc.news.sunsite.dk
X-Trace: 1713973124 news.sunsite.dk 709 luislupe@gmail.com/176.79.85.73:56424
X-Complaints-To: staff@sunsite.dk
 by: Luis Mendes - Wed, 24 Apr 2024 15:38 UTC

On Thu, 18 Apr 2024 19:52:42 -0700, et99 wrote:

> Hi Luis:
>
> A while back I really didn't know much about the tcl threads package.
> But then I bought Ashok's book. I can't recommend it more highly.
> Whatever technique you decide on, threads, event driven, channels,
> tclOO, it's all in the book with great examples you can copy/paste right
> into your program (from the pdf version). As Rich pointed out, you can
> read the tclOO chapter for free on line.
>
> -et

Hi et,

Yes, I've also bought the book and it has been my guide. Very good indeed.

Thank you,

Luís

1
server_pubkey.txt

rocksolid light 0.9.8
clearnet tor