🛠A Guy of Many Trades ðŸ›
Table of Contents generated with DocToc
-
🛠A Guy of Many TradesðŸ›
🛠A Guy of Many Trades ðŸ›
Structure
- Only Peer-Dependencies (except
cnd
,intertype
) - Sub-libraries accessible as
GUY.${library_name}
- Most sub-libraries implemented using
GUY.props.def_oneoff()
, therefore dependencies (which are declared peer dependencies) will only berequire()
d when needed.
Modules
GUY.props
: Common Operations on Object Properties
-
GUY.props.def: ( target, name, cfg ) ->
is just another name forObject.defineProperty()
. -
GUY.props.hide: ( object, name, value ) ->
is a shortcut to define a non-enumerable property as inObject.defineProperty object, name, { enumerable: false, value, }
. -
GUY.props.xray: ( owner, host ) ->
—A little bit the inverse toGUY.props.hide()
, it will return an object with all key/value pairs set that were found onowner
after applyingGUY.props.keys owner, { hidden: true, symbols: true, builtins: false, }
. Whenhost
is set, that value will be used to store the key/value pairs in, possibly overwriting existing keys. This method is useful for printing and debugging objects with non-enumerable and/or symbol keys. -
GUY.props.def_oneoff: ()
-
GUY.props.pick_with_fallback = ( d, fallback, keys... ) ->
—Given an objectd
, afallback
value and somekeys
, return an object that whosekeys
are the ones passed in, and whose values are either the same as found ind
, orfallback
in case a key is missing ind
or set toundefined
. Ifd[ key ]
isnull
, it will be replaced byfallback
. When no keys are given, an empty object will be returned. -
GUY.props.nullify_undefined = ( d ) ->
—Given an objectd
, return a copy of it where allundefined
values are replaced withnull
. In cased
isnull
orundefined
, an empty object will be returned. -
GUY.props.omit_nullish = ( d ) ->
—Given an objectd
, return a copy of it where allundefined
andnull
values are not set. In cased
isnull
orundefined
, an empty object will be returned. -
GUY.props.crossmerge = ( cfg ) ->
—Given an objectkeys
and an objectvalues
, return a new object whose keys come from the former and whose values come fom the latter. Should there be a key inkeys
that is not set invalues
, an error will be thrown unlessfallback
has been set (to any value, inlcudingundefined
). This method callsGUY.props.keys()
and will accept thecfg
settings used there (allow_any
,symbols
,builtins
,hidden
,depth
). -
GUY.props.has = ( x, key ) ->
—Given any valuex
, return whether the value has a given property (key
). This is a safe version ofReflect.has()
that never throws an error. Like direct property access (usingx.key
orx[ 'key' ]
) but unlikeObject.getOwnPropertyDescriptor()
&c,GUY.props.has()
looks into the prototype chain; likeObject.getOwnPropertyDescriptor()
, it does not trigger property getters. -
GUY.props.get = ( x, key, fallback ) ->
—Given any valuex
, return the value of property named inkey
. If that property is missing, throw an error, but whenfallback
has been given, returnfallback
instead. UsingGUY.props.get x, 'foo'
is like sayingx.foo
orx[ 'foo' ]
except that it doesn't tolerate missing property keys; using it with a fallback as inGUY.props.get x, 'foo', undefined
is like sayingx.foo
orx[ 'foo' ]
except that it also works fornull
andundefined
.
Using GUY.props.has()
and GUY.props.get()
it is always possible to circumvent errors being thrown and
instead do value-based error handling (and raise one own's errors where seen fit). One pattern to do so is
to define a private Symbol instead of relying on undefined
that could have been caused by all kinds of
circumstances. Here's a real-life example; it was created in a context where instances of the Strict_owner
class (for which see below) are prevalent (i.e. those objects will throw an error instead of returning
undefined
as is the standard in JS). This function will return the length
of a value x
where that
attribute is present (as in strings and arrays), or else its size
where that is present (as in sets and
maps), else it will return fallback
where given, or resort to throwing an error:
notavalue = Symbol 'notavalue'
size_of = ( x, fallback = misfit ) ->
return R unless ( R = GUY.props.get x, 'length', notavalue ) is notavalue
return R unless ( R = GUY.props.get x, 'size', notavalue ) is notavalue
return fallback unless fallback is misfit
throw new Error "expected an object with `x.length` or `x.size`, got a #{typeof x} with neither"
-
GUY.props.resolve_property_chain = ( owner, property_chain ) ->
—Given anowner
value (typically an object) and aproperty_chain
as a list of names, repeatedly apply the names, first against the owner, then the next name against that result and so on. This allows one to programmatically resolve chained property access and to parametrize a literalowner.first.second.other.last
asGUY.props.resolve_property_chain owner, [ 'first', 'second', 'other', 'last', ]
. -
GUY.props._shallow_copy = ( d ) ->
—Given objectd
, return a shallow copy of it using the technique demonstrated in the MDN documentation forObject.getOwnPropertyDescriptors()
:( d ) -> Object.create ( Object.getPrototypeOf d ), ( Object.getOwnPropertyDescriptors d )
. This method is not yet official part of the API because it may get an extension to work transparently with primitive values.
GUY.props.keys()
-
GUY.props.keys: () =>
—LikeObject.keys( t )
,Reflect.ownKeys( t )
(which is equivalent toObject.getOwnPropertyNames( target ).concat(Object.getOwnPropertySymbols( target ))
) but more versatile -
GUY.props.keys()
can retrieve or skip non-enumerable (a.k.a. 'hidden') keys -
GUY.props.keys()
can retrieve only own keys (with{ depth: 0 }
) or descend into the prototype chain for any number of steps:{ depth: null, }
(the default, equivalent to{ depth: Infinity, }
) will descend into the prototype chain or e.g.{ depth: 1, }
-
GUY.props.keys()
can retrieve string keys as well as symbol keys with{ symbols: true, }
-
GUY.props.keys()
works for all JS values, includingnull
andundefined
when{ allow_any: true, }
is set (the default) -
GUY.props.keys()
can retrieve hidden (i.e. non-enumerable) keys with{ hidden: true, }
-
GUY.props.keys()
can retrieve builtins with{ builtins: true, }
; observe that since builtins are always hidden,{ builtins: true, hidden: false, }
makes no sense and causes a validation error.
lst = [ 'x', ]
( Object.keys lst )
# [ '0', ]
( ( k for k of lst ) )
# [ '0', ]
( GUY.props.keys lst )
# [ '0', ]
( GUY.props.keys lst, { symbols: true, builtins: true, } )
# [ '0', 'length', 'constructor', 'concat', 'copyWithin', 'fill', 'find', 'findIndex', 'lastIndexOf',
# 'pop', 'push', 'reverse', 'shift', 'unshift', 'slice', 'sort', 'splice', 'includes', 'indexOf', 'join',
# 'keys', 'entries', 'values', 'forEach', 'filter', 'flat', 'flatMap', 'map', 'every', 'some', 'reduce',
# 'reduceRight', 'toLocaleString', 'toString', 'at', 'findLast', 'findLastIndex', Symbol.iterator,
# Symbol.unscopables, '__defineGetter__', '__defineSetter__', 'hasOwnProperty', '__lookupGetter__',
# '__lookupSetter__', 'isPrototypeOf', 'propertyIsEnumerable', 'valueOf', '__proto__' ]
( GUY.props.keys Array, { symbols: true, builtins: true, } )
# [ 'length', 'name', 'prototype', 'isArray', 'from', 'of', Symbol.species, 'arguments', 'caller',
# 'constructor', 'apply', 'bind', 'call', 'toString', Symbol.hasInstance, '__defineGetter__',
# '__defineSetter__', 'hasOwnProperty', '__lookupGetter__', '__lookupSetter__', 'isPrototypeOf',
# 'propertyIsEnumerable', 'valueOf', '__proto__', 'toLocaleString' ]
GUY.props.tree()
GUY.props.tree()
is the logical dual to GUY.props.keys()
: instead of descending into the prototype
chain, tree()
climbs through the key/value pairs attached to an object and, recursively, through the trees
of each value's key/value pairs. In default mode, the method returns a list of lists of names ('paths'):
log = console.log
{ inspect, } = require 'util'
d = { a: [ 0, 1, 2, ], e: { g: { some: 'thing', }, h: 42, h: null, }, empty: {}, }
for path in GUY.props.tree d
log inspect path
This will print:
[ 'a' ]
[ 'a', '0' ]
[ 'a', '1' ]
[ 'a', '2' ]
[ 'e' ]
[ 'e', 'g' ]
[ 'e', 'g', 'some' ]
[ 'e', 'h' ]
[ 'empty' ]
The default configuration for the tree()
method is { allow_any: true, symbols: false, builtins: false, hidden: false, depth: null, evaluate: null, sep: null, }
, with the last two settings being specific to
tree()
, and the first five having the same meaning as for GUY.props.keys()
, q.v.
To turn the result into a list of strings, pass in a property sep
:
...
for path in GUY.props.tree d, { sep: '.', }
...
'a'
'a.0'
'a.1'
'a.2'
'e'
'e.g'
'e.g.some'
'e.h'
'empty'
The evaluate
setting can be set to a function which will be called with an object { owner, key, value, }
where owner
is the current object being iterated over, and key
/value
the current property name and
value. The evaluate function should return true
, false
, or a string that may contain the words take
and/or descend
; the default (when no evaluate
function is given) is take,descend
, meaning all keys
will be both included in the list as well as descended into. true
is equivalent to take,descend
; false
means skip this property altogether. In the below example, we use the evaluate
setting to avoid descending
into arrays and to include only the tips (leaves, endpoints) of the tree:
evaluate = ({ owner, key, value, }) ->
return 'take' if Array.isArray value
return 'take' unless GUY.props.has_keys value
return 'descend'
...
for path in GUY.props.tree d, { evaluate, sep: '.', }
...
'a'
'e.g.some'
'e.h'
'empty'
Class for Strict Ownership
JavaScript is famously forgiving when it comes to accessing non-existing object properties. However this
lenience is also conducive to silent failure. Strict_owner
is an ES6 class that aims to provide users with
a convenient mechanism to produce object that throw an error when a non-existing property is being accessed.
When you extend your class with GUY.props.Strict_owner
, instance of your class will now throw an error
when a non-existing property is accessed.
Note Most often one will want to define a class that extends Strict_owner
, however, it is also
possible to pass in an arbitrary object—including a function—as property target
to the constructor, e.g.
f = ( x ) -> x * 2
x = new GUY.props.Strict_owner { target: f, }
Note As of Guy v6, special methods
get()
andset()
have been removed fromStrict_owner
. They have been replaced with a much cleaner and more correct implementation asGUY.props.get()
andGUY.props.has()
. These methods are more versatile, too, since they can be used with any JS value (including the always-problematicnull
andundefined
).
Note
GUY.props.Strict_owner
is implemented using an ES6Proxy
. While proxies are powerfool tools they're also notoriously hard to get right because of the many methods one could/should/must implement and which all have to act in mutually consistent ways. As a result, my personal recommendation here is to try hard not use a proxy and use another methodology to achieve a workable solution where possible. One thing that will probably not work is proxying a proxy: When you have aStrict_owner
object (which already is a proxy) and wrap it in another proxy, chances are you'll get errors likeinstance does not have property '0'
when attempting to usenode:util.inspect()
on that instance. I have been so far unable to fix that bug and since been looking for a solution that works without the need for a double proxy in that other codebase.
In order to get an object which is sealed and or frozen, use properties seal
and freeze
, respectively:
d = { x: 42, }
dso = new GUY.props.Strict_owner { target: d, seal: true, freeze: true, }
# this works:
dso.x # 42
# but none of these work:
# Strict owner ship means unknown properties cannot be accessed:
dso.y # Strict_owner instance does not have property 'y'
# Properties of a frozen object cannot be re-assigned:
dso.x = 48 # TypeError: Cannot assign to read only property 'x'
# Properties cannot be added to a sealed object (also implied by being frozen):
dso.y = 'something' # TypeError: Cannot define property y, object is not extensible
In addition, when property oneshot
is set (and neither seal
nor freeze
), properties can be set once,
but not get re-assigned. In case non-re-assignable values should also be immutable, consider to set frozen
objects:
d = { x: 42, }
dso = new GUY.props.Strict_owner { target: d, oneshot: true, }
dso.xy = new GUY.props.Strict_owner { target: { foo: 'bar', }, freeze: true, }
# dso.x = 123 # Strict_owner instance already has property 'x'
# dso.xy = {} # Strict_owner instance already has property 'xy'
# dso.xy.foo = 'gnu' # TypeError: Cannot assign to read only property 'foo'
Observe that because all of the sealing, freezing and one-shot business is performed on the proxy, not on the target object, we can still manipulate that one:
debug '^35345^', dso # { x: 42, xy: { foo: 'bar' } }
debug '^35345^', d # { x: 42, xy: { foo: 'bar' } }
# we *can* still manipulate the underlying object:
d.x = 123
debug '^35345^', dso # { x: 123, xy: { foo: 'bar' } }
debug '^35345^', d # { x: 123, xy: { foo: 'bar' } }
It is possible to lift the strict behavior of Strict_owner
instances by using Strict_owner.set_locked false
and resume strict behavior with Strict_owner.set_locked true
.
Special Keys
return "#{instance.constructor.name}" if key is Symbol.toStringTag
return target.constructor if key is 'constructor'
return target.toString if key is 'toString'
return target.call if key is 'call'
return target.apply if key is 'apply'
return target[ Symbol.iterator ] if key is Symbol.iterator
return target[ node_inspect ] if key is node_inspect
return target[ 0 ] if key is '0'
GUY.async
: Asynchronous Helpers
These 'five letter' methods are convenience methods in the sense that they are very thin shims over the somewhat less convenient JavaScript methods. For many people, the most strightforward way to understand what these methods do will be to read the very simple definitions:
every: ( dts, f ) -> setInterval f, dts * 1000
after: ( dts, f ) -> setTimeout f, dts * 1000
cease: ( toutid ) -> clearTimeout toutid
sleep: ( dts ) -> new Promise ( done ) => setTimeout done, dts * 1000
defer: ( f = -> ) -> await sleep 0; return await f()
In each case, dts
denotes an interval (delta time) measured in seconds (not milliseconds) and f
denotes a function. every()
and after()
return so-called timeout IDs (toutid
s), i.e. values that are
recognized by cease()
(clearTimeout()
, clearInterval()
) to stop a one-off or repetetive timed function
call. sleep()
returns a promise that should be awaited as in await sleep 3
, which will allow another
task on the event loop to return and resume execution no sooner than after 3000 milliseconds have elapsed.
Finally, there is defer()
, which should also be await
ed. It is a special use-case of sleep()
where the
timeout is set to zero, so the remaining effect is that other tasks on the event loop get a chance to run.
It accepts an optional function argument whose (synchronous or asynchronous) result will be returned.
GUY.nowait
: De-Asyncify JS Async Functions
Note Due to ongoing issues when compiling the deasync
module that this functionality
is implemented in, guy-nowait
has been removed from this release.
-
GUY.nowait.for_callbackable: ( fn_with_callback ) ->
—given an asynchronous functionafc
that accepts a NodeJS-style callback (as inafc v1, v2, ..., ( error, result ) -> ...
), returns a synchronous functionsf
that can be used without a callback (as inresult = sf v1, v2, ...
). -
GUY.nowait.for_awaitable: ( fn_with_promise ) ->
—given an asynchronous functionafp
that can be used withawait
(as inresult = await afp v1, v2, ...
) returns a synchronous functionf
that can be used withoutawait
(as inresult = sf v1, v2, ...
).
GUY.process
: Process-Related Utilities
Peer Dependencies: sindresorhus/exit-hook
-
GUY.process.on_exit: ( fn ) => ...
—callfn()
before process exits. Convenience link forsindresorhus/exit-hook
, which see for details. Note When installing this peer dependency, make sure to do so with the last CommonJS version added, as innpm install exit-hook@2.2.1
.
GUY.lft
: Freezing Objects
GUY.left.freeze()
and GUY.lft.lets()
provide access to the epynomous methods in
letsfreezethat
. freeze()
is basically
Object.freeze()
for nested objects, while d = lets d, ( d ) -> mutate d
provides a handy way to mutate
and re-assign a copy of a frozen object. See the
documentation for details.
GUY.fs
: File-Related Stuff
-
GUY.fs.walk_lines = ( path, cfg ) ->
—Given apath
, return a synchronous iterator over file lines. This is the most hassle-free approach to synchronously obtain lines of text files in NodeJS that I'm aware of, yet. The optionalcfg
argument may be an object with a single propertydecode
; when set tofalse
,walk_lines()
will iterate over buffers instead of strings. -
GUY.fs.walk_circular_lines = ( path, cfg ) ->
—Given apath
, return an iterator over the lines in the referenced file; optionally, when the iterator is exhausted (all lines have been read), restart from the beginning.cfg
may be an object with the keys:-
loop_count
—(cardinal; default:1
) controls how many times to loop over the file. Set to+Infinity
to allow for an unlimited number of laps. -
line_count
—(cardinal; default:+Infinity
) controls the maximum number of lines that will be yielded. - The iteration will finish as soon as the one or the other limit has been reached.
- By default,
GUY.fs.walk_circular_lines()
will act likeGUY.fs.walk_lines
. - The iterator will not yield anything when either
loop_count
orline_count
are set to0
.
-
-
GUY.fs.get_content_hash = ( path, cfg ) ->
—Given apath
, return the hexadecimalsha1
hash digest for its contents. On Linux, this usessha1sum
, andshasum
on all other systems.
GUY.src
: JS Source Code Analysis
This submodule needs peer-dependencies, install them with
npm install acorn acorn-loose acorn-walk astring
or
pnpm add acorn acorn-loose acorn-walk astring
-
@STRICT_PARSER = require 'acorn'
-
@LOOSE_PARSER = require 'acorn-loose'
-
@AST_walk = require 'acorn-walk'
-
@ASTRING = require 'astring'
-
@parse = ( cfg ) =>
—Given either a JS sourcetext
or afunction
, return an ESTree-compliant AST. Should an error occur andfallback
is set to any value, that value will be returned; otherwise, the error will be thrown. Theuse
parameter controls which parser is used and can take on the values'strict'
,'strict,loose'
, and'loose'
. For many settings'strict,loose'
will probably the right setting since the strict parser will balk already on unnamed function declarations that are not part of assignment, while the 'loose' parser happily (and correctly parses those). For this reason, the default setting isuse: 'strict,loose'
.
-
slug_node_from_simple_function = ( cfg ) =>
—Same asslug_from_simple_function()
, below, but returns an AST node representing the result. You can manipulate the node if you want it and then pass it toGUY.src._generate()
to get it rendered as JS, but the result will slightly differ from whatslug_from_simple_function()
would return for the same input because that function does some post-processing on the source text to make it even terser. -
slug_from_simple_function = ( cfg ) =>
—Given the samecfg
object one would use forGUY.src.parse()
, return a 'slug' (i.e. a condensed form) of its source text. This slug is defined to be- if function has no return:
undefined
- if function has single
return
:argument
property of theReturnStatement
node - if function has several
return
s:firstlastBlockStatement
(i.e. the function body) (???)
- if function has no return:
Examples:
â–ˆ ( -> )
''
â–ˆ ( ( x ) -> 42 )
'42'
â–ˆ ( ( x ) -> ( not x? ) or ( @isa.object x ) or ( @isa.nonempty.text x ) )
'x == null || this.isa.object(x) || this.isa.nonempty.text(x)'
â–ˆ ( `function ( x ) { 42; }` )
'42;'
â–ˆ ( `function ( x ) { return 42; }` )
'42'
â–ˆ ( ( x ) -> if x? then true else false )
'if (x != null) { return true; } else { return false; }'
â–ˆ ( ( x ) -> ( not x? ) or ( @isa.object x ) or ( @isa.nonempty.text x ) )
'x == null || this.isa.object(x) || this.isa.nonempty.text(x)'
â–ˆ f3
'if (x > 0) { return true; } if (x < 0) { return false; } return null;'
GUY.trm
- Preview version, expect changes
- colorize terminal output
- two variants of
node:util.inspect()
:GUY.trm.inspect()
,GUY.trm.rpr()
- standardized loggers:
alert()
,debug()
,help()
,info()
,plain()
,praise()
,urge()
,warn()
,whisper()
- produce with
GUY.trm.get_loggers badge
wherebadge
identifies your submodule - includes short timestamp
- writer
GUY.trm.log()
writes undecorated stuff toprocess.stderr()
- writer
GUY.trm.echo()
writes undecorated stuff toprocess.stdout()
- all writers and loggers apply
GUY.trm.rpr
to each argument independently, soecho a, b, c
is likeconsole.log ( rpr a ), ( rpr b ), ( rpr c )
- writer
GUY.trm.pen()
returns string representation as used inGUY.trm.log()
&c -
GUY.trm.strip_ansi: ( text ) ->
uses RegEx from (chalk)[https://raw.githubusercontent.com/chalk/ansi-regex/main/index.js] to strip ANSI codes from a given string
GUY.sets
Operations on sets, copied / modelled on JavaScript for impatient programmers (ES2022 edition): Missing Set operations
-
unite: ( P... ) ->
—return the union of all sets passed in -
intersect: ( P... ) ->
—return the intersection of all sets passed in -
subtract: ( a, b ) ->
—return the set of elements of seta
except for those that are also in setb
GUY.temp
GUY.temp
provides context handlers to work with temporary files and directories. It is built on top of
temp
.
GUY.temp.with_file: ( cfg, handler ) ->
GUY.temp.with_directory: ( cfg, handler ) ->
#-----------------------------------------------------------------------------------------------------------
@GUY_temp_context_handler_file = ( T, done ) ->
GUY = require '../../../apps/guy'
#.........................................................................................................
do =>
path = null
info = GUY.temp.with_file ({ path: mypath, fd, }) ->
path = mypath
T?.ok isa.fs_file mypath
T?.eq info, { files: 1, dirs: 0, }
T?.ok not isa.fs_file path
#.........................................................................................................
return done?()
#-----------------------------------------------------------------------------------------------------------
@GUY_temp_context_handler_directory = ( T, done ) ->
GUY = require '../../../apps/guy'
#.........................................................................................................
do =>
path = null
info = GUY.temp.with_directory ({ path: mypath, }) ->
path = mypath
debug '^34534^', { path, }
T?.ok isa.fs_directory mypath
T?.eq info, { files: 0, dirs: 1, }
T?.ok not isa.fs_directory path
#.........................................................................................................
do =>
path = null
info = GUY.temp.with_directory { prefix: 'whatever-', }, ({ path: mypath, }) ->
path = mypath
debug '^34534^', { path, }
T?.ok ( PATH.basename mypath ).startsWith 'whatever-'
T?.ok isa.fs_directory mypath
T?.eq info, { files: 0, dirs: 1, }
T?.ok not isa.fs_directory path
#.........................................................................................................
return done?()
To Do
-
[–] adopt
icql-dba/errors#Dba_error
:class @Dba_error extends Error constructor: ( ref, message ) -> super() @message = "#{ref} (#{@constructor.name}) #{message}" @ref = ref return undefined
- [–] might want to integrate code from https://github.com/creemama/utiljs/blob/master/packages/utiljs-errors/lib/RethrownError.js to enable re-throwing of errors w/out losing stack trace info.
- also see https://github.com/joyent/node-verror
-
[–] while
test @[ "nowait with async steampipes" ]
works in isolation, running the test suite hangs indefinitely. -
[+]
see whetherBenchmarks show that patched version with suitable chunk size performs OK; using patched version to avoid deprecation warning.n-readlines
may be replaced by a simpler, faster implementation as it does contain some infelicitous loops in_searchInBuffer
that can probably be replaced bybuffer.indexOf()
. Also, there's a similar implementation inintertext-splitlines
. -
[–]
GUY.fs.walk_lines()
: allow to configure; maketrimEnd()
the default -
implement easy way to collect, rediect
process.stdout
,process.stderr
:original_stderr_write = process.stderr.write.bind process.stderr collector = [] process.stderr.write = ( x ) -> echo '^3453^', type_of x # FS.writeSync output_fd, x collector.push x original_stderr_write '^1234^ ' original_stderr_write x echo "(echo) helo world" info "(info) helo world" info "whatever goes on here" warn CND.reverse "is collected" for x in collector process.stdout.write '^collector@4565^' + x
-
[–] take over tabulation (as in
hengist/src/helpers
) -
[–] could the
SQL
string annotation / tagged literal function be syntactically extended to allow simpler interpolation of escaped names? Could we instantiate it with a dictionary of values?
-
[–]
GUY.src.get_first_return_clause_text()
:-
[–] change input format to standard
cfg
-based to make compatible with call conventions forGUY.src.parse()
-
[–] use
fallback
argument to decide whether to return value or throw error in case of parsing failure (same forparse()
) -
[–] return based on how many
ReturnStatement
s are found:- if function has no return:
undefined
- if function has single
return
:argument
property of theReturnStatement
node - if function has several
return
s:firstlastBlockStatement
(i.e. the function body) (???)
- if function has no return:
-
[–] change input format to standard
-
[–] move
GUY.src._generate()
to public API -
[–] what should be the correct output for
GUY.src.slug_from_simple_function { function: ( -> ), }
? Currently it is the empty string which is not ideal -
[–] use
Reflect.has()
to check for property availability forStrict_owner
s instead of using instance method; provide asGUY.props.has()
-
[–] likewise, use
GUY.props.get: ( target, name, fallback = misfit ) ->
instead of instance method -
[–] consider to move submodule
_builtins
, test for builtins to Intertype, backport to Intertype-legacy -
[–] consider to make
trm.log()
write tostdout
,err()
tostderr
-
[–] implement
trm.write()
write tostdout
without trailing newline (but formatting likelog()
) -
[–] consider to drop
trm.pen()
, use improvedGUY.trm.rpr()
instead -
[–] fix
GUY.props.get '', 'length'
(works for sets but not for string)s -
[–] consider to re-implement
deep_copy()
fromletsfreezethat
using -
[–] remove dependencies as far as possible, make GUY run in browser
LOUPE = require '../deps/loupe.js' @rpr = rpr = ( x ) => LOUPE.inspect x, { customInspect: false, } @xrpr = ( x ) -> ( rpr x )[ .. 1024 ]
-
[–] modify behavior of
GUY.props.tree()
:-
[–] callbacks are
take_key()
,descend_value()
,filter_path()
-
[–]
take_key: ( owner, key, value ) ->
-
[–]
descend_value: ( owner, key, value ) ->
-
[–]
filter_path: ( owner, path ) ->
; path will be list of strings w/outsep
, single string with it -
[+] implement
GUY.props.walk_tree()
-
[–] callbacks are
-
[–] modify behavior of
GUY.trm.rpr()
:- [–] colorize for readablity
-
[–] change signature to either
rpr x, cfg
orrpr P...
-
[–] in either case, provide a way to pass configuration to
node:util.inspect
- [–] allow indented output
-
[–] implement method that allows to name a type and give a cfg object, returns cfg for named typed based on
crossmatch()
ing defaults for that type with givencfg
-
[–]
GUY.trm.warn()
,alert()
: collect and re-issue on process exit; may want to throw error or at least issue non-zero error code when messages beyond (configurable) urgency threshold were issued; this will allow apps to not bail out prematurely on minor issues and keep messages from getting hidden in regular messages -
[–] turn
Strict_owner
instances into sealed objects, retaining old behavior in new classStrict_getter
-
[–] turn
GUY
, submodules intoStrict_owner
instances -
[–] move deep freezing, deep sealing from
lft
toprops
? -
[–] concerning
Strict_owner
: inhengist/dev/intertype/_ng.test.coffee
we have found a way to build custom-named functions with strict ownership:@_demo_type_cfgs_as_funmctions_2 = -> class Intertype create_type_cfg: ( cfg ) -> defaults = { extras: true, collection: false, type: null, } cfg = { defaults..., cfg..., } name = cfg.type R = ( ( x ) -> x ** 2 ).bind @ Object.defineProperty R, 'name', { value: name, } R[ k ] = v for k, v of cfg R = new GUY.props.Strict_owner target: R Object.seal R # R = GUY.lft.freeze R1 = R # <== doesn't freeze??? Object.freeze R # <== works as expected return R types = new Intertype() urge '^982-1^', f = types.create_type_cfg { type: 'foobar', } # Object is frozen, sealed, and has a strict `get()`ter: urge '^982-2^', Object.isFrozen f urge '^982-3^', Object.isSealed f try f.collection = true catch error then warn rvr error.message # Cannot assign to read only property 'collection' of function 'function () { [native code] }' try f.xxx catch error then warn rvr error.message # ^guy.props.Strict_owner@1^ Strict_owner instance does not have property 'xxx' try f.xxx = 111 catch error then warn rvr error.message # Cannot define property xxx, object is not extensible info '^982-4^', f.name info '^982-5^', f 42 return null
This should lead to the following:
-
[–] fix the apparent bug that
GUY.lft.freeze()
does not freeze a function -
[–] an extension of
GUY.props.Strict_owner
:-
[+] incorporate (deep?)
seal
ing (next tofreeze
ing) -
[–] allow to pass in a 'deep target' such that properties not found on the immediate target (the
named function in the above) will also be searched in the deep target (such as the
Intertype
orDbay
instance), mimicking a prototype chain
-
[+] incorporate (deep?)
-
[–] fix the apparent bug that
-
[–] integrate pinned package versions helper, cf
( require 'mixa/lib/check-package-versions' ) require '../pinned-package-versions.json'
Is Done
-
[+] make choice between parsers configurable:
- only
acorn
- first
acorn
, upon parse erroracorn-loose
- only
acorn-loose
- only
-
[+]
parse()
: usefallback
argument to decide whether to return value or throw error in case of parsing failure -
[+] testing for a key without inadvertantly retrieving its value is surprisingly involved in JS.
Re-implement
GUY.props.has()
- (1) using
Reflect.has()
catchingTypeError: Reflect.has called on non-object
, andObject.getPrototypeOf()
, or - (2) using attribute access
x[ key ]
, discarding value and catching errors from strict owners,null
andundefined
. - option (2) should be faster, maybe just live with the fact that attribute checking without value retrieval is not very viable in JS
- depending on favorable benchmarks, may want to cache whether instances of a given type are collections
with a size and if so, which property (
length
orsize
) is usedstructuredClone
; benchmarks
- (1) using
-
[+] rename
GUY.props.has_keys()
->GUY.props.has_any_keys()