guy

npm dependencies checker

Usage no npm install needed!

<script type="module">
  import guy from 'https://cdn.skypack.dev/guy';
</script>

README

A Guy of Many Trades

Table of Contents generated with DocToc

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 be require()d when needed.

Modules

guy.props: Define Properties

  • guy.props.def: ( target, name, cfg ) -> is just another name for Object.defineProperty().

  • guy.props.hide: ( object, name, value ) -> is a shortcut to define a non-enumerable property as in Object.defineProperty object, name, { enumerable: false, value, }.

  • guy.props.def_oneoff: ()

guy.async: Asynchronous Helpers

  • guy.async.defer: ( f ) ->—equivalent to setImmediate f

  • guy.async.after: ( dts, f ) ->—equivalent to setTimeout 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.

guy.nowait: De-Asyncify JS Async Functions

Note Due to ongoing issues when compiling the deasync module that this functionality is implemented in, guy-nowaithas been removed from this release.

**Peer Dependencies**: [`abbr/deasync`](https://github.com/abbr/deasync)
  • guy.nowait.for_callbackable: ( fn_with_callback ) ->—given an asynchronous function afc that accepts a NodeJS-style callback (as in afc v1, v2, ..., ( error, result ) -> ...), returns a synchronous function sf that can be used without a callback (as in result = sf v1, v2, ...).

  • guy.nowait.for_awaitable: ( fn_with_promise ) ->—given an asynchronous function afp that can be used with await (as in result = await afp v1, v2, ...) returns a synchronous function f that can be used without await (as in result = sf v1, v2, ...).

guy.cfg: Instance Configuration Helper

  • guy.cfg.configure_with_types: ( self, cfg = null, types = null ) => ...—Given a class instance self, an optional cfg object and an optional Intertype-like types instance,

    • set clasz to self.constructor for conciseness;
    • derive effective cfg from defaults (where clasz.C.defaults.constructor_cfg is set) and argument cfg;
    • 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() with self;
    • in declare_types(), clients are encouraged to declare type constructor_cfg and validate self.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 to configure_with_types().
    • It is always possible to declare or import types on the client's module level and pass in that object to configure_with_types(); this will avoid (most of) the overhead of per-instance computations and use the same types 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 same types 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.

guy.process: Process-Related Utilities

Peer Dependencies: sindresorhus/exit-hook

  • guy.process.on_exit: ( fn ) => ...—call fn() before process exits. Convenience link for sindresorhus/exit-hook, which see for details. Note When installing this peer dependency, make sure to do so with the last CommonJS version added, as in npm 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' } }

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.obj: Common Operations on Objects

  • guy.obj.pick_with_fallback = ( d, fallback, keys... ) ->—Given an object d, a fallback value and some keys, return an object that whose keys are the ones passed in, and whose values are either the same as found in d, or fallback in case a key is missing in d or set to undefined. If d[ key ] is null, it will be replaced by fallback. When no keys are given, an empty object will be returned.

  • guy.obj.nullify_undefined = ( d ) ->—Given an object d, return a copy of it where all undefined values are replaced with null. In case d is null or undefined, an empty object will be returned.

  • guy.obj.omit_nullish = ( d ) ->—Given an object d, return a copy of it where all undefined and null values are not set. In case d is null or undefined, an empty object will be returned.

guy.fs: File-Related Stuff

  • guy.fs.walk_lines = ( path, cfg ) ->—Given a path, 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 optional cfg argument may be an object with a single property decode; when set to false, walk_lines() will iterate over buffers instead of strings.

  • guy.fs.walk_circular_lines = ( path, cfg ) ->—Given a path, 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 like guy.fs.walk_lines.
    • The iterator will not yield anything when either loop_count or line_count are set to 0.
  • guy.fs.get_content_hash = ( path, cfg ) ->—Given a path, return the hexadecimal sha1 hash digest for its contents. On Linux, this uses sha1sum, and shasum 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
    
  • [–] while test @[ "nowait with async steampipes" ] works in isolation, running the test suite hangs indefinitely.

  • [+] see whether n-readlines may be replaced by a simpler, faster implementation as it does contain some infelicitous loops in _searchInBuffer that can probably be replaced by buffer.indexOf(). Also, there's a similar implementation in intertext-splitlines. Benchmarks show that patched version with suitable chunk size performs OK; using patched version to avoid deprecation warning.

  • [–] guy.fs.walk_lines(): allow to configure; make trimEnd() 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