README
A Guy of Many Trades
Table of Contents generated with DocToc
- Structure
- Modules
guy.cfg
: Instance Configuration Helperguy.lft
: Freezing Objectsguy.obj
: Common Operations on Objectsguy.fs
: File-Related Stuff- To Do
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
: Define Properties guy.props
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.def_oneoff: ()
: Asynchronous Helpers guy.async
guy.async.defer: ( f ) ->
—equivalent tosetImmediate f
guy.async.after: ( dts, f ) ->
—equivalent tosetTimeout f, dts * 1000
. Observe that Δt must be given in seconds (not milliseconds).guy.async.sleep: ( dts ) ->
—await sleep 1
will resume after one second.
: De-Asyncify JS Async Functions guy.nowait
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, ...
).
: Instance Configuration Helper guy.cfg
guy.cfg.configure_with_types: ( self, cfg = null, types = null ) => ...
—Given a class instanceself
, an optionalcfg
object and an optional Intertype-liketypes
instance,- set
clasz
toself.constructor
for conciseness; - derive effective
cfg
from defaults (whereclasz.C.defaults.constructor_cfg
is set) and argumentcfg
; - make
cfg
a frozen instance property. - Procure
types
where not given and - make it a non-enumerable instance property.
- Now call class method
clasz.declare_types()
withself
; - in
declare_types()
, clients are encouraged to declare typeconstructor_cfg
and validateself.cfg
; - further, other types may be declared as appropriate; since those types have access to
self.cfg
, their definition may depend on those parameters. - The return value of
clasz.declare_types()
is discarded; clients that want to provide their own must pass it as third argument toconfigure_with_types()
. - It is always possible to declare or import
types
on the client's module level and pass in that object toconfigure_with_types()
; this will avoid (most of) the overhead of per-instance computations and use the sametypes
object for all instances (which should be good enough for most cases). - One thing to avoid though is to declare types on the module level, then pass that types object to
configure_with_types()
to add custom types at instantiation time. Doing so would share the sametypes
object between instances and modify it for each new instance, which is almost never what you want. Either declare one constant types object for all instances or else build a new bespoke types object for each instance from scratch.
- set
: Process-Related Utilities guy.process
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
.
Usage Examples
Most Minimal (Bordering Useless)
It is allowable to call configure_with_types()
with an instance of whatever class.
guy.cfg.configure_with_types()
will look for properties clasz.C.defaults
, clasz.declare_types()
(and a
types
object as third argument to configure_with_types()
) and provide defaults where missing:
class Ex
constructor: ( cfg ) ->
guy.cfg.configure_with_types @, cfg
#.........................................................................................................
ex1 = new Ex()
ex2 = new Ex { foo: 42, }
#.........................................................................................................
log ex1 # Ex { cfg: {} }
log ex1.cfg # {}
log ex2 # Ex { cfg: { foo: 42 } }
log ex2.cfg # { foo: 42 }
log ex1.types is ex2.types # false
log type_of ex1.types.validate # function
More Typical
class Ex
@C: guy.lft.freeze
foo: 'foo-constant'
bar: 'bar-constant'
defaults:
constructor_cfg:
foo: 'foo-default'
bar: 'bar-default'
@declare_types: ( self ) ->
self.types.declare 'constructor_cfg', tests:
"@isa.object x": ( x ) -> @isa.object x
"x.foo in [ 'foo-default', 42, ]": ( x ) -> x.foo in [ 'foo-default', 42, ]
"x.bar is 'bar-default'": ( x ) -> x.bar is 'bar-default'
self.types.validate.constructor_cfg self.cfg
return null
constructor: ( cfg ) ->
guy.cfg.configure_with_types @, cfg
return undefined
#.......................................................................................................
ex = new Ex { foo: 42, }
log ex # Ex { cfg: { foo: 42, bar: 'bar-default' } }
log ex.cfg # { foo: 42, bar: 'bar-default' }
log ex.constructor.C # { foo: 'foo-constant', bar: 'bar-constant', defaults: { constructor_cfg: { foo: 'foo-default', bar: 'bar-default' } } }
log ex.constructor.C?.defaults # { constructor_cfg: { foo: 'foo-default', bar: 'bar-default' } }
: Freezing Objects guy.lft
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.
: Common Operations on Objects guy.obj
guy.obj.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.obj.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.obj.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.
: File-Related Stuff guy.fs
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.
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 defaultimplement 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