The /lib/dbug.hoon agent wrapper adds support to view the state of a Gall agent.  It is applied to an existing Gall agent as a single drop-in line, %-  agent:dbug.
Before we look at the code, let's consider the functionality it exposes.  By supplying %-  agent:dbug, an associated +dbug generator can be invoked against the agent state.
For instance, using the %azimuth agent, we can expose the current state of the agent:
> :azimuth +dbug[ %7url=~.net=%defaultrefresh=~m5whos={}nas=[%0 points={} operators={} dns=<||>]own={}spo={}logs=~sap=[%0 id=[hash=0x0 number=0] nas=[%0 points={} operators={} dns=<||>] owners={} sponsors={}]]> :azimuth +dbug %bowl> [ [our=~zod src=~zod dap=%azimuth][wex={} sup={}]act=3eny0v1rn.n49dr.2u8t5.h7be5.6dcq7.9hon5.6m3pr.3hcb8.u7tmv.qddpq.kent7.1ftc7.9tao6.hfsht.4i0c3.ak3t7.t8d8j.nn4eb.b7eh3.4d5pr.t8ftgnow=~2023.2.3..20.03.23..f60ebyk=[p=~zod q=%base r=[%da p=~2023.1.26..02.41.25..926a]]]> :azimuth +dbug [%incoming ~]> no matching subscriptions> :azimuth +dbug [%state '(lent whos)']> 0
There are four actions exposed by the wrapper via the +dbug generator:
- :app +dbugexposes the entire state, just dumping the current agent state.
- :app +dbug %bowlshows the agent's- bowl. The Gall- bowlconsists of:+$ bowl :: standard app state$: $: our=ship :: hostsrc=ship :: guestdap=term :: agent== ::$: wex=boat :: outgoing subssup=bitt :: incoming subs== ::$: act=@ud :: change numbereny=@uvJ :: entropynow=@da :: current timebyk=beak :: load source== == ::
- :app +dbug [%state 'hoon']exposes data in the state, including evaluated Hoon like- (lent values).
- :app +dbug [?(%incoming outgoing) specifics]reveals details about the subscriptions.
The Code
/gen/dbug.hoon:
Click to expand
/+ *dbug:- %say|= $: :: environment*:: inline argumentsargs=?(~ [what=?(%bowl %state) ~] [=poke ~]):: named arguments~==:- %dbug?- args~ [%state ''][@ ~] ?-(what.args %bowl [%bowl ~], %state [%state ''])[[@ *] ~] poke.args==
/lib/dbug.hoon:
Click to expand
:: dbug: agent wrapper for generic debugging tools:::: usage: %-(agent:dbug your-agent)::|%+$ poke$% [%bowl ~][%state grab=cord][%incoming =about][%outgoing =about]==::+$ about$@ ~$% [%ship =ship][%path =path][%wire =wire][%term =term]==::++ agent|= =agent:gall^- agent:gall!.|_ =bowl:gall+* this .ag ~(. agent bowl)::++ on-poke|= [=mark =vase]^- (quip card:agent:gall agent:gall)?. ?=(%dbug mark)=^ cards agent (on-poke:ag mark vase)[cards this]=/ dbug!<(poke vase)=; =tang((%*(. slog pri 1) tang) [~ this])?- -.dbug%bowl [(sell !>(bowl))]~::%state=? grab.dbug =('' grab.dbug) '-'=; product=^vase[(sell product)]~=/ state=^vase:: if the underlying app has implemented a /dbug/state scry endpoint,:: use that vase in place of +on-save's.::=/ result=(each ^vase tang)(mule |.(q:(need (need (on-peek:ag /x/dbug/state)))))?:(?=(%& -.result) p.result on-save:ag)%+ slap(slop state !>([bowl=bowl ..zuse]))(ream grab.dbug)::%incoming=; =tang?^ tang tang[%leaf "no matching subscriptions"]~%+ murn%+ sort ~(tap by sup.bowl)|= [[* a=[=ship =path]] [* b=[=ship =path]]](aor [path ship]:a [path ship]:b)|= [=duct [=ship =path]]^- (unit tank)=; relevant=??. relevant ~`>[path=path from=ship duct=duct]<?: ?=(~ about.dbug) &?- -.about.dbug%ship =(ship ship.about.dbug)%path ?=(^ (find path.about.dbug path))%wire %+ lien duct|=(=wire ?=(^ (find wire.about.dbug wire)))%term !!==::%outgoing=; =tang?^ tang tang[%leaf "no matching subscriptions"]~%+ murn%+ sort ~(tap by wex.bowl)|= [[[a=wire *] *] [[b=wire *] *]](aor a b)|= [[=wire =ship =term] [acked=? =path]]^- (unit tank)=; relevant=??. relevant ~`>[wire=wire agnt=[ship term] path=path ackd=acked]<?: ?=(~ about.dbug) &?- -.about.dbug%ship =(ship ship.about.dbug)%path ?=(^ (find path.about.dbug path))%wire ?=(^ (find wire.about.dbug wire))%term =(term term.about.dbug)====::++ on-peek|= =path^- (unit (unit cage))?. ?=([@ %dbug *] path)(on-peek:ag path)?+ path [~ ~][%u %dbug ~] ``noun+!>(&)[%x %dbug %state ~] ``noun+!>(on-save:ag)[%x %dbug %subscriptions ~] ``noun+!>([wex sup]:bowl)==::++ on-init^- (quip card:agent:gall agent:gall)=^ cards agent on-init:ag[cards this]::++ on-save on-save:ag::++ on-load|= old-state=vase^- (quip card:agent:gall agent:gall)=^ cards agent (on-load:ag old-state)[cards this]::++ on-watch|= =path^- (quip card:agent:gall agent:gall)=^ cards agent (on-watch:ag path)[cards this]::++ on-leave|= =path^- (quip card:agent:gall agent:gall)=^ cards agent (on-leave:ag path)[cards this]::++ on-agent|= [=wire =sign:agent:gall]^- (quip card:agent:gall agent:gall)=^ cards agent (on-agent:ag wire sign)[cards this]::++ on-arvo|= [=wire =sign-arvo]^- (quip card:agent:gall agent:gall)=^ cards agent (on-arvo:ag wire sign-arvo)[cards this]::++ on-fail|= [=term =tang]^- (quip card:agent:gall agent:gall)=^ cards agent (on-fail:ag term tang)[cards this]----
As we examine this code, there are two particularly interesting aspects:
- How /lib/dbug.hoonmodifies an agent's arms by adding functionality over the top of them.
- How /gen/dbug.hoonutilizes the modified arms with an elegant and simple invocation.
There is also extensive use of tank/tang formatted error messaging.
How the library works
By applying this door builder using %- censig, the ++on-poke and ++on-peek arms can be modified.  (In fact, all of the arms can be modified but most of the arms are pass-throughs to the modified agent.)
++on-poke
Click to expand
++ on-poke|= [=mark =vase]^- (quip card:agent:gall agent:gall)?. ?=(%dbug mark)=^ cards agent (on-poke:ag mark vase)[cards this]=/ dbug!<(poke vase)=; =tang((%*(. slog pri 1) tang) [~ this])?- -.dbug%bowl [(sell !>(bowl))]~::%state=? grab.dbug =('' grab.dbug) '-'=; product=^vase[(sell product)]~=/ state=^vase:: if the underlying app has implemented a /dbug/state scry endpoint,:: use that vase in place of +on-save's.::=/ result=(each ^vase tang)(mule |.(q:(need (need (on-peek:ag /x/dbug/state)))))?:(?=(%& -.result) p.result on-save:ag)%+ slap(slop state !>([bowl=bowl ..zuse]))(ream grab.dbug)::%incoming=; =tang?^ tang tang[%leaf "no matching subscriptions"]~%+ murn%+ sort ~(tap by sup.bowl)|= [[* a=[=ship =path]] [* b=[=ship =path]]](aor [path ship]:a [path ship]:b)|= [=duct [=ship =path]]^- (unit tank)=; relevant=??. relevant ~`>[path=path from=ship duct=duct]<?: ?=(~ about.dbug) &?- -.about.dbug%ship =(ship ship.about.dbug)%path ?=(^ (find path.about.dbug path))%wire %+ lien duct|=(=wire ?=(^ (find wire.about.dbug wire)))%term !!==::%outgoing=; =tang?^ tang tang[%leaf "no matching subscriptions"]~%+ murn%+ sort ~(tap by wex.bowl)|= [[[a=wire *] *] [[b=wire *] *]](aor a b)|= [[=wire =ship =term] [acked=? =path]]^- (unit tank)=; relevant=??. relevant ~`>[wire=wire agnt=[ship term] path=path ackd=acked]<?: ?=(~ about.dbug) &?- -.about.dbug%ship =(ship ship.about.dbug)%path ?=(^ (find path.about.dbug path))%wire ?=(^ (find wire.about.dbug wire))%term =(term term.about.dbug)====
The ++on-poke arm has several branches added to it after a check to see whether it is being used through the +dbug generator.  If it isn't (as determined by the associated mark), then the poke is passed through to the base agent.
?. ?=(%dbug mark)=^ cards agent (on-poke:ag mark vase)[cards this]
The following ?- wuthep handles the input arguments:  %state is the most interesting code in this library.  The code first checks whether the base agent has a /dbug/state peek endpoint already (in which case it passes it through), otherwise it evaluates the requested Hoon expression against the agent's state (obtained via ++on-save:ag).
%state=? grab.dbug =('' grab.dbug) '-'=; product=^vase[(sell product)]~=/ state=^vase:: if the underlying app has implemented a /dbug/state scry endpoint,:: use that vase in place of +on-save's.::=/ result=(each ^vase tang)(mule |.(q:(need (need (on-peek:ag /x/dbug/state)))))?:(?=(%& -.result) p.result on-save:ag)%+ slap(slop state !>([bowl=bowl ..zuse]))(ream grab.dbug)
This branch includes the use of a rare =? tiswut conditional leg change and the reversed =/ tisfas, =; tismic.  There is also some direct compilation of cords taking place:
- ++sellis a- vasepretty-printer.
- ++slopconses two- vases together as a cell.
- ++slapcompiles a Hoon expression and produces a- vaseof the result.
- ++reamparses a- cordto a Hoon expression.
++on-peek
Click to expand
++ on-peek|= =path^- (unit (unit cage))?. ?=([@ %dbug *] path)(on-peek:ag path)?+ path [~ ~][%u %dbug ~] ``noun+!>(&)[%x %dbug %state ~] ``noun+!>(on-save:ag)[%x %dbug %subscriptions ~] ``noun+!>([wex sup]:bowl)==
The ++on-peek arm adds several peek endpoints which expose the state (via ++onsave:ag) and the subscriptions.
> .^(noun %gx /(scot %p our)/azimuth/(scot %da now)/dbug/subscriptions/noun)[0 0]
How the generator works
The generator explicitly injects the %dbug mark in its return cask ([mark noun]).  This is a valid if uncommon operation, and it works here because the mark is never used as a transforming gate but only as a marker to see whether the arms need to pass through the values.  The no-argument input is routed through the %state with an empty cord.
:- %dbug?- args~ [%state ''][@ ~] ?-(what.args %bowl [%bowl ~], %state [%state ''])[[@ *] ~] poke.args==
Library authors should consider augmenting developer capabilities by exposing appropriate functionality using a wrapper agent similar to /lib/dbug.