clay(3tcl)

πŸ“¦
version 2.0 Β· 2 versions
, in debian trixie, ubuntu questing, ubuntu resolute, ubuntu stonking

🌸 NAME 🌸

okay bestie, welcome to clay(3tcl) β€” straight outta the Clay Framework manual, section 3tcl, and it is giving "minimalist framework for large scale OO Projects". ✨

clay β€” a minimalist framework for large scale OO Projects. That's the whole name section, no notes. πŸŽ€


✨ SYNOPSIS ✨

package require Tcl 8.6 9
package require uuid
package require oo::dialect
proc clay::PROC name arglist body ?ninja ?
proc clay::_ancestors resultvar class
proc clay::ancestors ?args?
proc clay::args_to_dict ?args?
proc clay::args_to_options ?args?
proc clay::dynamic_arguments ensemble method arglist ?args?
proc clay::dynamic_wrongargs_message arglist
proc clay::is_dict d
proc clay::is_null value
proc clay::leaf ?args?
proc clay::K a b
proc clay::noop ?args?
proc clay::cleanup
proc clay::object_create objname ?class ?
proc clay::object_rename object newname
proc clay::object_destroy ?args?
proc clay::path ?args?
proc clay::putb ?map? text
proc clay::script_path
proc clay::NSNormalize qualname
proc clay::uuid_generate ?args?
proc clay::uuid::generate_tcl_machinfo
proc clay::uuid::tostring uuid
proc clay::uuid::fromstring uuid
proc clay::uuid::equal left right
proc clay::uuid cmd ?args?
proc clay::tree::sanitize dict
proc clay::tree::_sanitizeb path varname dict
proc clay::tree::storage rawpath
proc clay::tree::dictset varname ?args?
proc clay::tree::dictmerge varname ?args?
proc clay::tree::merge ?args?
proc dictargs::proc name argspec body
proc dictargs::method name argspec body
proc clay::dialect::Push class
proc clay::dialect::Peek
proc clay::dialect::Pop
proc clay::dialect::create name ?parent ?
proc clay::dialect::NSNormalize namespace qualname
proc clay::dialect::DefineThunk target ?args?
proc clay::dialect::Canonical namespace NSpace class
proc clay::dialect::Define namespace class ?args?
proc clay::dialect::Aliases namespace ?args?
proc clay::dialect::SuperClass namespace ?args?
proc clay::dynamic_methods class
proc clay::dynamic_methods_class thisclass
proc clay::define::Array name ?values ?
proc clay::define::Delegate name info
proc clay::define::constructor arglist rawbody
proc clay::define::Class_Method name arglist body
proc clay::define::class_method name arglist body
proc clay::define::clay ?args?
proc clay::define::destructor rawbody
proc clay::define::Dict name ?values ?
proc clay::define::Option name ?args?
proc clay::define::Method name argstyle argspec body
proc clay::define::Option_Class name ?args?
proc clay::define::Variable name ?default ?
proc clay::ensemble_methodbody ensemble einfo
proc clay::define::Ensemble rawmethod ?args?
proc clay::event::cancel self ?task *?
proc clay::event::generate self event ?args?
proc clay::event::nextid
proc clay::event::Notification_list self event ?stackvar ?
proc clay::event::notify rcpt sender event eventinfo
proc clay::event::process self handle script
proc clay::event::schedule self handle interval script
proc clay::event::subscribe self who event
proc clay::event::unsubscribe self ?args?
proc clay::singleton name script
method clay ancestors
method clay dump
method clay find path ?path...?
method clay get path ?path...?
method clay GET path ?path...?
method clay merge dict ?dict...?
method clay replace dictionary
method clay search path ?path...?
method clay set path ?path...? value
method clay ancestors
method clay cache path value
method clay cget field
method clay delegate ?stub? ?object?
method clay dump
method clay ensemble_map
method clay eval script
method clay evolve
method clay exists path ?path...?
method clay flush
method clay forward method object
method clay get path ?path...?
method clay leaf path ?path...?
method clay merge dict ?dict...?
method clay mixin class ?class...?
method clay mixinmap ?stub? ?classes?
method clay provenance path ?path...?
method clay replace dictionary
method clay search path valuevar isleafvar
method clay source filename
method clay set path ?path...? value
method InitializePublic

πŸ’… DESCRIPTION πŸ’…

okay so here's the whole vibe: Clay introduces a method ensemble to both oo::class and oo::object called clay. That ensemble handles all of the high level interactions within the framework. Clay stores structured data. Clay manages method delegation. Clay also has facilities to manage the complex interactions that come about with mixins. 🫢

The central concept is that inside of every object and class (which are actually objects too, no cap) there is a dict called clay. What's stored in that dict is left to the imagination. But because this dict is exposed via a public method, we can share structured data between object, classes, and mixins. πŸ’–


🌈 STRUCTURED DATA 🌈

Clay uses a standardized set of method interactions and introspection that TclOO already provides to perform on-the-fly searches. And bestie, on-the-fly is the whole point: it means the data is never stale, and we avoid many of the sorts of collisions that would arise when objects start mixing in other classes during operation. ✨

The clay methods for both classes and objects have a get and a set method. For objects, get will search through the local clay dict. If the requested leaf is not found, or the query is for a branch, the system will then begin to poll the clay methods of all of the class that implements the object, all of that classes' ancestors, as well as all of the classes that have been mixed into this object, and all of their ancestors. πŸ‘‘

Intended branches on a tree end with a directory slash (/). Intended leaves are left unadorned. This is a guide for the tool that builds the search results to know what parts of a dict are intended to be branches and which are intended to be leaves. For simple cases, branch marking can be ignored:

::oo::class create ::foo { }
::foo clay set property/ color blue
::foo clay set property/ shape round

set A [::foo new]
$A clay get property/
{color blue shape round}

$A clay set property/ shape square
$A clay get property/
{color blue shape square}

But when you start storing blocks of text, guessing what field is a dict and what isn't gets messy:

::foo clay set description {A generic thing of designated color and shape}

$A clay get description
{A generic thing of designated color and shape}

Without a convention for discerning branches for leaves what should have been a value can be accidentally parsed as a dictionary, and merged with all of the other values that were never intended to be merge. Here is an example of it all going wrong:

::oo::class create ::foo { }
# Add description as a leaf
::foo clay set description  {A generic thing of designated color and shape}
# Add description as a branch
::foo clay set description/  {A generic thing of designated color and shape}

::oo::class create ::bar {
  superclass foo
}
# Add description as a leaf
::bar clay set description  {A drinking establishment of designated color and shape and size}
# Add description as a branch
::bar clay set description/  {A drinking establishment of designated color and shape and size}

set B [::bar new]
# As a leaf we get the value verbatim from he nearest ancestor
$B clay get description
  {A drinking establishment of designated color and shape and size}
# As a branch we get a recursive merge
$B clay get description/
{A drinking establishment of designated color and size thing of}

πŸ¦‹ CLAY DIALECT πŸ¦‹

Clay is built using the oo::dialect module from Tcllib. oo::dialect allows you to either add keywords directly to clay, or to create your own metaclass and keyword set using Clay as a foundation. For details on the keywords and what they do, consult the functions in the ::clay::define namespace. πŸ’œ


πŸŽ€ METHOD DELEGATION πŸŽ€

Method Delegation It is sometimes useful to have an external object that can be invoked as if it were a method of the object. Clay provides a delegate ensemble method to perform that delegation, as well as introspect which methods are delegated in that manner. All delegated methods are marked with html-like tag markings (< >) around them. 🌸

::clay::define counter {
  Variable counter 0
  method incr {{howmuch 1}} {
    my variable counter
    incr counter $howmuch
  }
  method value {} {
    my variable counter
    return $counter
  }
  method reset {} {
    my variable counter
    set counter 0
  }
}
::clay::define example {
  variable buffer
  constructor {} {
    # Build a counter object
    set obj [namespace current]::counter
    ::counter create $obj
    # Delegate the counter
    my delegate <counter> $obj
  }
  method line {text} {
    my <counter> incr
    append buffer $text
  }
}

set A [example new]
$A line {Who’s line is it anyway?}
$A <counter> value
1

πŸ’… COMMANDS πŸ’…

okay bestie, buckle up, because this is the whole command roster πŸŽ€ every proc in the package, every arglist, every example, and not one single thing left out. some of these have lore, some just have a name and a dream. both are valid. let's go πŸ’–

  • proc clay::PROC name arglist body ?ninja ? β€” so here's the situation: lots of features in this package might get added as commands to future Tcl cores, or show up in binary form from other packages. so we need a declarative way of saying "make this command if there isn't one already." and the ninja argument? that's the script to execute if the command does get created by this mechanism. stealthy. iconic. πŸ₯·
  • proc clay::_ancestors resultvar class β€” (the og page doesn't spill any extra tea here, just the signature 🫢)
  • proc clay::ancestors ?args? β€” (no description given, just vibes ✨)
  • proc clay::args_to_dict ?args? β€” (signature only, bestie)
  • proc clay::args_to_options ?args? β€” (signature only πŸ’…)
  • proc clay::dynamic_arguments ensemble method arglist ?args? β€” (no extra notes)
  • proc clay::dynamic_wrongargs_message arglist β€” (signature only)
  • proc clay::is_dict d β€” (no description)
  • proc clay::is_null value β€” (no description)
  • proc clay::leaf ?args? β€” (no description)
  • proc clay::K a b β€” (no description)
  • proc clay::noop ?args? β€” does absolutely nothing, and does it beautifully. slay. it's lowkey so useful in prototyping for commenting out blocks of code without actually having to comment them out. and it also makes a handy default for method delegation when a delegate hasn't been assigned yet. no-op, but make it fashion 🌈
  • proc clay::cleanup β€” processes the queue of objects that are waiting to be destroyed. the janitor of the clay world 🧹
  • proc clay::object_create objname ?class ? β€” (signature only, no notes)
  • proc clay::object_rename object newname β€” (signature only)
  • proc clay::object_destroy ?args? β€” marks an object for destruction on the next cleanup. not instant, bestie, it's a to-do list situation 🫢
  • proc clay::path ?args? β€” (no description)
  • proc clay::putb ?map? text β€” appends a line of text to a variable. optionally you can apply a string mapping while you're at it ✨
  • proc clay::script_path β€” (signature only)
  • proc clay::NSNormalize qualname β€” (no description)
  • proc clay::uuid_generate ?args? β€” (no description)
  • proc clay::uuid::generate_tcl_machinfo β€” (signature only)
  • proc clay::uuid::tostring uuid β€” (no description)
  • proc clay::uuid::fromstring uuid β€” converts a string representation of a uuid into its binary format. glow-down, if you will πŸ’…
  • proc clay::uuid::equal left right β€” compares two uuids for equality. are they twins or not, we must know 😀
  • proc clay::uuid cmd ?args? β€” the uuid dispatcher. the subcommands it knows are:
    • uuid generate -> string rep of a new uuid
    • uuid equal uuid1 uuid2
  • proc clay::tree::sanitize dict β€” outputs a dictionary, removing any . entries that got added by clay::tree::merge. spring cleaning for your dict 🧼
  • proc clay::tree::_sanitizeb path varname dict β€” helper function for ::clay::tree::sanitize. it formats the string representation for a dictionary element inside a human-readable stream of lines, and figures out if it needs to call itself again with more indentation to express a sub-branch. recursion, but make it aesthetic πŸ¦‹
  • proc clay::tree::storage rawpath β€” returns the path as a storage path for clay::tree, with all branch terminators removed. it'll also break arguments up if they contain /. watch it work:
> clay::tree::storage {foo bar baz bang}
foo bar baz bang
> clay::tree::storage {foo bar baz bang/}
foo bar baz bang
> clay::tree::storage {foo bar baz bang:}
foo bar baz bang:
> clay::tree::storage {foo/bar/baz bang:}
foo bar baz bang:
> clay::tree::storage {foo/bar/baz/bang}
foo bar baz bang
  • proc clay::tree::dictset varname ?args? β€” sets an element inside a recursive dictionary, marking all the branches on the way down to the final element. if the value doesn't exist in the nested dict, it gets added as a leaf. if the value already exists as a branch, the value you give gets merged in β€” but only if it's a valid dict. and if the incoming value is not a valid dict? then it just overrides whatever was stored, and from that point on it's treated as a leaf. periodt. watch:
> set r {}
> ::clay::tree::dictset r option color default Green
. {} option {. {} color {. {} default Green}}
> ::clay::tree::dictset r option {Something not dictlike}
. {} option {Something not dictlike}
# Note that if the value is not a dict, and you try to force it to be
# an error with be thrown on the merge
> ::clay::tree::dictset r option color default Blue
missing value to go with key
  • proc clay::tree::dictmerge varname ?args? β€” a recursive form of dict merge, made for modifying variables in place. like this:
> set mydict {sub/ {sub/ {description {a block of text}}}}
> ::clay::tree::dictmerge mydict {sub/ {sub/ {field {another block of text}}}}]
> clay::tree::print $mydict
sub/ {
  sub/ {
    description {a block of text}
    field {another block of text}
  }
}
> set mydict {sub/ {sub/ {description {a block of text}}}}
> set odict [clay::tree::merge $mydict {sub/ {sub/ {field {another block of text}}}}]
> clay::tree::print $odict
sub/ {
  sub/ {
    description {a block of text}
    field {another block of text}
  }
}
  • proc dictargs::proc name argspec body β€” named procedures as a new command ✨
  • proc dictargs::method name argspec body β€” (signature only)
  • proc clay::dialect::Push class β€” (no description)
  • proc clay::dialect::Peek β€” (no description)
  • proc clay::dialect::Pop β€” (no description)
  • proc clay::dialect::create name ?parent ? β€” this proc generates a namespace, a "mother of all classes", and a rudimentary set of policies for this dialect. she does it all πŸ’…
  • proc clay::dialect::NSNormalize namespace qualname β€” support commands; not intended to be called directly. hands off, bestie πŸ™ˆ
  • proc clay::dialect::DefineThunk target ?args? β€” (no description)
  • proc clay::dialect::Canonical namespace NSpace class β€” (no description)
  • proc clay::dialect::Define namespace class ?args? β€” the implementation of the language's define command πŸ‘‘
  • proc clay::dialect::Aliases namespace ?args? β€” (signature only)
  • proc clay::dialect::SuperClass namespace ?args? β€” (signature only)
  • proc clay::dynamic_methods class β€” (no description)
  • proc clay::dynamic_methods_class thisclass β€” (no description)
  • proc clay::define::Array name ?values ? β€” new OO keywords for clay, bestie πŸŽ€
  • proc clay::define::Delegate name info β€” an annotation that objects of this class interact with delegated methods. the annotation is meant to be a dictionary, and the only reserved key is description β€” a human-readable description 🫢
  • proc clay::define::constructor arglist rawbody β€” (no description)
  • proc clay::define::Class_Method name arglist body β€” specifies a method for the class object itself, instead of for objects of the class. main character energy for the class πŸ‘‘
  • proc clay::define::class_method name arglist body β€” an alias to the new Class_Method keyword ✨
  • proc clay::define::clay ?args? β€” (no description)
  • proc clay::define::destructor rawbody β€” (no description)
  • proc clay::define::Dict name ?values ? β€” (signature only)
  • proc clay::define::Option name ?args? β€” defines an option for the class πŸ’…
  • proc clay::define::Method name argstyle argspec body β€” (no description)
  • proc clay::define::Option_Class name ?args? β€” defines a class of options. every field / value pair will be inherited by any option that specifies name as its class field. inheritance but for your options, we love to see it 🌸
  • proc clay::define::Variable name ?default ? β€” this keyword can also be expressed as:
property variable NAME {default DEFAULT}

variables registered in the variable property also get initialized (if they're missing) when the object changes class via the morph method. glow-up via morph ✨

  • proc clay::ensemble_methodbody ensemble einfo β€” produces the body of an ensemble's public dispatch method. ensemble is the name of the ensemble. einfo is a dictionary of methods for the ensemble, and each value is a script to execute on dispatch. like so:
::clay::ensemble_methodbody foo {
  bar {tailcall my Foo_bar {*}$args}
  baz {tailcall my Foo_baz {*}$args}
  clock {return [clock seconds]}
  default {puts "You gave me $method"}
}
  • proc clay::define::Ensemble rawmethod ?args? β€” (no description)
  • proc clay::event::cancel self ?task *? β€” cancels a scheduled event. not me hitting undo on the timeline 😭
  • proc clay::event::generate self event ?args? β€” generates an event. it adds a subscription mechanism so objects can see who has received this event, which prevents spamming or infinite recursion. very mindful, very demure 🧚
  • proc clay::event::nextid β€” (signature only)
  • proc clay::event::Notification_list self event ?stackvar ? β€” called recursively to produce a list of who receives notifications πŸ“‹
  • proc clay::event::notify rcpt sender event eventinfo β€” final delivery to the intended recipient object. special delivery, bestie πŸ“¦πŸ’–
  • proc clay::event::process self handle script β€” evaluates an event script in the global namespace 🌍
  • proc clay::event::schedule self handle interval script β€” schedules an event to occur later. future plans, we're manifesting ⏰
  • proc clay::event::subscribe self who event β€” subscribes an object to an event pattern. hit that follow button πŸ””
  • proc clay::event::unsubscribe self ?args? β€” unsubscribes an object from an event pattern. we're healing πŸ’œ
  • proc clay::singleton name script β€” an object that's intended to be its own class. she's independent, she doesn't need a class, she is the class πŸ‘‘

🌈 CLASSES 🌈

πŸ‘‘ CLASS CLAY::CLASS πŸ‘‘

✨ Methods ✨

  • method clay ancestors β€” Returns this class plus every single ancestor, all lined up in search order. The whole fam, no gatekeeping. πŸ’–
  • method clay dump β€” Returns a complete dump of this object's clay data β€” and only this object's clay data, bestie. No ancestor data sneaking in. πŸ™…β€β™€οΈ
  • method clay find path ?path...? β€” Pulls a chunk of data out of the clay system. If the last element of path is a branch, you get a recursive merge of all data from this object and its constituent classes for that branch. If the last element is a leaf, it searches this object for a matching leaf, or searches all constituent classes for a matching leaf and returns the first value found. If nothing is found, it returns an empty string. When a branch is returned, the topmost . entry is omitted. ✨
  • method clay get path ?path...? β€” Pulls a chunk of data from the class's clay system. If no value is found, returns an empty string. If a branch is returned, the topmost . entry is omitted.
  • method clay GET path ?path...? β€” Pulls a chunk of data from the class's clay system. If no value is found, returns an empty string. (Same energy as get, just without that topmost . omission note. πŸ’…)
  • method clay merge dict ?dict...? β€” Recursively merges the dictionaries you pass into the object's local clay storage. Glow-up for your data. 🫢
  • method clay replace dictionary β€” Replaces the contents of the internal clay storage with the dictionary given. Out with the old, in with the new. πŸ”₯
  • method clay search path ?path...? β€” Returns the first matching value for the path in either this class's clay data or one of its ancestors. First match wins, periodt.
  • method clay set path ?path...? value β€” Merges the contents of value with the object's clay storage at path.

πŸ¦‹ CLASS CLAY::OBJECT πŸ¦‹

clay::object β€” this class is inherited by every class that has options. Main character inheritance energy. πŸ‘‘

✨ Methods ✨

  • method clay ancestors β€” Returns the class this object belongs to, all classes mixed into this object, and all ancestors of those classes in search order. The full guest list. πŸŽ€
  • method clay cache path value β€” Stores VALUE in such a way that a request in SEARCH for PATH will always return it until the cache is flushed. Cached and unbothered. πŸ’…
  • method clay cget field β€” Pulls a value from either the object's clay structure or one of its constituent classes that matches the field name. The order of search is:
    1. As a value in the local dict variable config
    2. As a value in the local dict variable clay
    3. As a leaf in any ancestor as a root of the clay tree
    4. As a leaf in any ancestor as const field
    5. As a leaf in any ancestor as option field default
  • method clay delegate ?stub? ?object? β€” Introspect or control method delegation. With no arguments, it returns a key/value list of stubs and objects. With just the stub argument, it returns the object (if any) attached to that stub. With a stub and an object, this command forwards all calls to the method stub over to the object. Go little stub, go. 🐝
  • method clay dump β€” Returns a complete dump of this object's clay data, plus the data from all constituent classes recursively blended in. Everything, all mixed together like a perfect smoothie. πŸ“
  • method clay ensemble_map β€” Returns a dictionary describing the method ensembles to be assembled for this object. The blueprint, bestie. πŸ“‹
  • method clay eval script β€” Evaluates a script in the namespace of this object.
  • method clay evolve β€” Triggers the InitializePublic private method. PokΓ©mon evolution but for clay. ✨
  • method clay exists path ?path...? β€” Returns 1 if the path exists in either the object's clay data. Values greater than one mean the element exists in one of the object's constituent classes. A value of zero means the path could not be found. Simple math, slay.
  • method clay flush β€” Wipes any caches built by the clay implementation. Clear the cache, clear the mind. πŸ§˜β€β™€οΈ
  • method clay forward method object β€” A convenience wrapper for:
oo::objdefine [self] forward {*}$args
  • method clay get path ?path...? β€” Pulls a chunk of data from the clay system. If the last element of path is a branch (ends in a slash /), returns a recursive merge of all data from this object and its constituent classes for that branch. If the last element is a leaf, it searches this object for a matching leaf, or searches all constituent classes for a matching leaf and returns the first value found. If no value is found, returns an empty string.
  • method clay leaf path ?path...? β€” A modified get that's tailored to pull only leaf elements. Leaves only, no branches allowed. πŸƒ
  • method clay merge dict ?dict...? β€” Recursively merges the dictionaries given into the object's local clay storage. Same merge magic as before. 🫢
  • method clay mixin class ?class...? β€” Performs [oo::objdefine [self] mixin] on this object, with a few extra rules: Before the call, for any class that was previously mixed in but is not in the new result, execute the script registered to mixin/ unmap-script (if given). For all new classes that were not present prior to this call, after the native TclOO mixin is invoked, execute the script registered to mixin/ map-script (if given). For all classes that are now present and "mixed in", execute the script registered to mixin/ react-script (if given). Mixin glow-up with callbacks, we love to see it. πŸ’–
  • method clay mixinmap ?stub? ?classes? β€” With no arguments, returns the map of stubs and classes mixed into the current object. When only stub is given, returns the classes mixed in on that stub. When stub and classlist are given, replaces the classes currently on that stub with the given classes and invokes clay mixin on the new matrix of mixed-in classes. Map it out, bestie. πŸ—ΊοΈ
  • method clay provenance path ?path...? β€” Returns either self if that path exists in the current object, or returns the first class (if any) along the clay search path which contains that element. Where did it come from? We investigate. πŸ”
  • method clay replace dictionary β€” Replaces the contents of the internal clay storage with the dictionary given.
  • method clay search path valuevar isleafvar β€” Returns true, and sets valuevar to the value and isleafar to true or false if PATH was found in the cache. (Yes, the variable name is written as isleafar in the original β€” keeping it exactly as it is. 😭)
  • method clay source filename β€” Sources the given filename within the object's namespace.
  • method clay set path ?path...? value β€” Merges the contents of value with the object's clay storage at path.
  • method InitializePublic β€” Instantiates variables. Called on object creation and during clay mixin.

πŸ‘‘ AUTHORS πŸ‘‘

Sean Woods mailto:<yoda@etoyoc.com>


πŸ› BUGS, IDEAS, FEEDBACK πŸ›

This document, and the package it describes, will undoubtedly contain bugs and other problems. (Not me being dramatic, it literally says so. πŸ™ˆ) Please report such in the category oo of the Tcllib Trackers [http://core.tcl.tk/tcllib/reportlist]. Please also report any ideas for enhancements you may have for either package and/or documentation.

When proposing code changes, please provide unified diffs, i.e. the output of diff -u.

Note further that attachments are strongly preferred over inlined patches. Attachments can be made by going to the Edit form of the ticket immediately after its creation, and then using the left-most button in the secondary navigation bar. Do it for the plot. 🫢


πŸ”‘ KEYWORDS πŸ”‘

TclOO, oo


πŸ“š CATEGORY πŸ“š

Programming tools


Copyright (c) 2018 Sean Woods <yoda@etoyoc.com>


That's the whole clay(3tcl) page, bestie β€” from the tcllib source, version 0.8.8, dated back in the day. Go forth and merge your clay data recursively. Slay. πŸ’…βœ¨

πŸ“¦ where this page comes from