This is a grab-bag of the most idiosyncratic functions that I keep writing in each project, so I'm MIT/NPM'ing them just so I can use yarn instead of cut/paste.
Usage
const u = require('@ashnazg/utils'); // https://ashnazg.com/docs/lib/utils/
mkdtemp
Adds two tricks to node's mkdtempSync
- on process exit, the resulting folder will be
rm -rf
ed - parent directories are created (and never cleaned out) as necessary:
u.mkdtempNew('/foo/bar/baz')
will create baz123456 as a short lived dir; /foo/bar will persist.
mktempNew
(This'll be renamed to mktemp() after the legacy use cases for the Old ones below have been moved off the main name.)
Because node only exposes the underlying mkdtemp function and not mktemp, we depend on the OS only for random-unique folders and as far as auto-generated file names within that, we just use an incrementing int.
const abs_filename = u.mktempNew({
prefix: '/tmp/nodeapp-', // 6 random chars are appended to this to make the folder name that'll be autodeleted on process exit
base: 'page-', // within the newly created folder, each file starts with this, has 0-based id appended, and
ext: undefined, // if this is defined, filenames end with `.${ext}`
});
if prefix
is the same as a previous call to u.mktemp, it reuses the same mkdtemp results in order to not spam process.exit hooks.
The serial int used is shared by all mktemp calls; it's not reset to zero for each unique prefix. Caller is responsible for using base:
to ensure any manually created filenames can't overlap with autogenerated filenames.
old mktemp/mkdtemp/mkdtempOnce
mkdtempOnce([prefix])
is the same as mkdtemp but only creates ONE temporary folder per unique prefix given -- this is
parent folders are not removed on process exit
mkdtemp always returns with a trailing slash, but the presence of a trailing slash in input changes behavior (see fs.mkdtemp())
const folder_name = u.mkdtemp(); // returns /tmp/nodeapp-${RNG}/
const folder_name = u.mkdtemp($HOME+'/rando/'); // returns ~/rando/${RNG}/
note that only the child folder is removed on process exit; mkdtemp('/tmp/fubar/')
uses /tmp/fubar/ABC but leaks fubar, and mkdtemp('/tmp/long/path')
uses /tmp/long/pathABC but leaks long/. So best practice for a truely clean usage is mkdtemp('/tmp/name')
u.mktemp is using mkdtempOnce under the hood, so you don't need to care about file name races.
const file_name = u.mktemp(); // returns /tmp/nodeapp-${RNG}/${N} where N is unique within this run of the process
const fn = u.mktemp({prefix: 'foo'}); // uses this for the underlying mkdtemp() folder name
const fn = u.mktemp({base: 'ribbit'}); // returns .../ribbit${N}
const fn = u.mktemp({ext: 'csv'}); // returns .../${N}.csv
Misc
better typeof
var u = require('@ashnazg/utils'); // ~/projects/ashnazg-npm/utils/utils.js
var poor_answer1 = typeof null; // "object"
var poor_answer2 = typeof []; // "object"
var better1 = u.type(null); // "null"
var better2 = u.type([]); // "array"
structured stack tracing
function haslogicbug() {
u.die("the momwraths aren't gyring correctly");
}
result:
FAIL: @ashnazg/utils/test.js:23:haslogicbug() the momwraths aren't gyring correctly
misc oneliners
u.iso.today() // => 2020-01-01
u.iso.now() // => 2020-01-01T22:14:04.390Z
var {lo} = require('@ashnazg/utils');
lo(...args) // logs args after pretty-printing each as JSON, with tabs,
lo
exists because I'm sick of writing console.log + JSON a hundred times per day. So I reduced it to two letters to save on carpal.
if you thought lo
didn't need to exist, wait til you see @ashnazg/literalizer!
Release 0.3.N
Dusty Refactor, avoid using -- I'm polishing a refactor where all browser-friendly utilities are available as const u = require('@ashnazg/utils/browser')
-- I've done local yarn link
testing, but to get the coverage I need to be sure, I need to publish a build or two that may have borked dependencies between internal chunks...
Release 0.3.0
- breaking change: moved utils.q to new package: quantum-promise
- Added processing hooks to u.boolify: each hook takes the original val as a param:
u.boolify(val, {onTruth, onFalse, onNullish, onInvalidType, onInvalidString}); // defaults: true false false throw throw
- An 'on' config can be a non-function value; in that case, it's just returned.
- wrote u.prettyPrintError for excessively colorized error writing to console.
Release 0.2.7
Created a mocha-specific tool for turning on extra verbosity (or whatever you want) on tests that failed on the previous run of this specific describe() group.
USAGE:
const autoVerbosity = require('@ashnazg/utils/auto-verbose');
describe('group', function() {
autoVerbosity({
on(task_title) {
modify state to increase verbosity -- is only called on jobs who were in last run's fail list.
}, off(task_title, task_status) {
modify state to reset verbosity -- runs after _every_ test, regardless of prior failure-ness.
},
cache: '/UNIQUE-path-for-this-describe' // for example, '/tmp/mocha-GROUPTITLE'
});
it('tests stuff', ...);
});
Release 0.2.6
u.readdir(path, {opts})
returns an async iterator over all the files (or other entries).- I made this because I couldn't find a lib that definitely handled backpressure, the lack of which was a deal breaker.
- opts allow you to control what entities, where to recurse, and whether you want the type or just the name:
files = true, symlinks = true, recurse = true, recurse_symlinks = false, // NOT implemented yet: I want to put in a cycle-detector first. types = false, // return {name, type} instead of just 'name' dirs = false, pipes = false, sockets = false, blocks = false, character_devices = false,
u.merge(base, ...srcs) is like Object.assign, except it smart-merges arrays and maps all the way through the tree.
tweaks to u.class(foo) when foo is an object -- iterable types are preferred over returning plain old object, but a constructor name trumps both of those.
Release 0.2.5
Removed the "level=FAIL" param from u.complain(why, level) in favor of making all of the complain-based tools accept varargs: u.complain('server error', network_request)
Release 0.2.4
Overhauled the fail/warn logs.
- removed stdout entirely, so I can pipe with no noise in the stream, and all noise into error logs:
emits_errors | emits_other_errors | process | save
- u.fatalUserError is deprecated in favor of u.quit(...printables) or the less exity u.fail(...)
- want a stack?
- u.complain(err, level=FAIL) either prints err's stack, or if err is a string, uses its own stack to figure out what file/line to drop into stderr.
- u.die(err) is complain+exit() (if process.exitCode is not an error yet, make it one)
- don't want a stack?
- u.warn(...printables)
- u.fail(...printables)
- u.quit(...printables) is fail+exit() (if process.exitCode is not an error yet, make it one)
- want a named info stream that measures time between calls to it?
const logger = u.info('NETWORK'); logger("Do I even have a network?"); logger("I finished my tests."); $ASHNAZG_LOG_NETWORK=1 node ./myapp.js INFO[NETWORK]: Do I even have a network? INFO[NETWORK]: I finished my tests. (0.001s)
Also, u.curl() gained a flag that allows small response bodies to be loaded into RAM -- just syntactic sugar so I don't have to write parse(readFile()) so much.
const rec = await u.curl({src: 'https...', include_body_max_mb: 42})
- rec.dst always exists, but rec.response.body will be set if the file length is not over the limit
- rec.response.body is a json object if the body parses without errors, and a raw utf8 otherwise.
Release 0.2.3
u.curl now has a ttl_days_fail mode so you can have a different cache duration on http errors. It also tracks the duration of the curl call in the metadata now.
Release 0.2.2
- u.curl now has the option of saving errors to the metadata cache, and if reusing a cache entry, throwing/returning as if this was a live call
- u.class now distinguishes (async) iterable from general objects.
Release 0.2.1
- Bugfixing curl/shell tools
- added my tiny shim of promise tools inspired by Kriskowal's Q
Release 0.2.0
u.exists(fn)
is like fs.stat() except it returns false on NOENT instead of throwingu.chowngrp(fn {owner: -1, group: -1})
supports both uid/gid as well as 'user' and 'group'u.shell("cmd foo", opts)
=>{cmd, code: 0, signal: 'SIGINT', killed: false, stdout: '', stderr: ''}
- opts.max_code (default 0) determines what counts as a reject vs resolve. (magic value -1 means never throw.)
u.symlink(existing, new)
is here just for symmetry; I find it habit-disruptive for a few tools to be f(dst, src) when everything else is f(src, dst)u.hardlink(src, dst, opts)
if (opts.or_cp), falls back on u.cp if the link operation fails due to crossing a filesystem boundary.- It, u.cp, and u.mv now use
u.chmod(opts)
to ensure consistent final attributes in all implementation paths. - bugfix: u.mv() would fall apart if the initial rename() did work.
{base: 'page-'}
option added tou.mktemp({})
mode.- small but backwards-compatibility breaking change:
u.mktemp('foo')
is now interpreted asu.mktemp({base: 'foo'})
and notu.mktemp({fn:'foo'})
- manually switching your invocation to
{fn: 'foo'}
will retain old behavior. - this is going to be logged to stderr for one or more releases while things transition before the new behavior goes back to "not acting deprecated"
old way:
const fn1 = u.mktemp('foo');
const fn2 = u.mktemp('foo');
assert(fn1 === fn2);
new way:
const fn1 = u.mktemp('foo');
const fn2 = u.mktemp('foo');
assert(fn1 !== fn2);
Release 0.1.10
- u.class: it's like u.type, but for objects, it returns classy names if possible, and for functions, returns 'generator' and 'async_generator' where the function's metadata is one. (Caveat: has nothing to do with whether the function returns an iterable, since any function could do so.)
- split u.mkdtemp out from the implementation of u.mktemp for use cases that just want the directory.
- u.mv and u.cp both take (src, dst, optional_stream_write_opts)
- do not use this release see 0.2.1
Release 0.1.9
Bugfix: u.assert.throw({run() {throw "string"}})
now works as well as run() {throw new Error("string");}
Release 0.1.8
Bugfix: u.assert.throw({run() {...}})
wasn't awaiting on run(), so it was only working for sync throws.
Release 0.1.7
- getEnv() extracts secrets from the environment into a convenient map
- cache() uses https module to make a local disk cache without touching content details like encodings
- undent and removeDeadLines
- parsePickyDate is picky and supports a subset of ISO: it only allows an explicit 'Z' timezone, and it only allows fractionals on seconds.
- why not Date()? because I want to know when my upstreams' formatting strays.
- quit() is like die() but avoids printing stack traces; die is for code bugs; quit is for external problems like network down.
- md5 now supports explicitly setting encoding.
- md5.file takes a filename instead of data value
- +u.cheapHash a simple hasher for no-risk use cases. (shoutout to Mike McShaffry)
assert(){run: () => throw 'thing', expected: 'thing', cleanup: () => 'released OS handles'})
is like assert.throws, except this handles promises.
Release 0.1.6
Added more oneliners like md5; mucked about greatly with 1.5's error/stack output. (now with async promise stacks not being ignored!)
- md5
- now(optional date) returns an ISO date string in prod and a human-friendly one in dev.
- now.asFilename(...) is the same but only using ascii characters that are trivial in HTTP/windows/linux filename usage (colons are right out.)
- divertConsole(...) returns a console equivalent for ease of switching between dev-screen and prod-syslog
- u.complain / u.die / etc now shows a 'FAIL:' on stdout and any trace on stderr
Release 0.1.5
Added:
Func | What |
---|---|
chopHOME(path) | which turns /home/$USER/foo into ~/foo or /home/otherperson/foo into ~otherperson/foo |
chopMain(path) | if path is under your node main's path, make it relative to that. |
chopPWD(path) | if path is under your PWD, make it relative to that. |
findGoodLookingStackFrame(key) | finds a stackframe that has your search_key in the file path and isn't "log()". (key defaults to your main module's folder name) |
Release 0.1.4
- Added u.getFrame(offset=1) which returns `{file, func, line}`` of your parent frame. (or grandparent for offset>1)
- Added options to u.u.getStack(shift,keep,raw) to support that.
- shift: skip these early frames
- keep: return only this many.
- raw: return the v8 objects, not string tuples
Release 0.1.3
Added a good enough for now template tag called undent
for making pretty heredocs in JS. It doesn't handle interpolations yet, as I didn't need any for today's use case.
Release 0.1.2
Added a centralized copy of my estlint rules that I like to plug into each FE or BE project's config.
Release 0.1.1
u.fatalUserError previously accepted only an err-like {message:''}
; it now also accepts a string or printable that doesn't have a message field.
Release 0.1.0
Fixed a typo in u.type that meant the test for null was broken.