twitst4tz

twitter statistics web application
Log | Files | Refs | README | LICENSE

README.md (10776B)


      1 # jsprim: utilities for primitive JavaScript types
      2 
      3 This module provides miscellaneous facilities for working with strings,
      4 numbers, dates, and objects and arrays of these basic types.
      5 
      6 
      7 ### deepCopy(obj)
      8 
      9 Creates a deep copy of a primitive type, object, or array of primitive types.
     10 
     11 
     12 ### deepEqual(obj1, obj2)
     13 
     14 Returns whether two objects are equal.
     15 
     16 
     17 ### isEmpty(obj)
     18 
     19 Returns true if the given object has no properties and false otherwise.  This
     20 is O(1) (unlike `Object.keys(obj).length === 0`, which is O(N)).
     21 
     22 ### hasKey(obj, key)
     23 
     24 Returns true if the given object has an enumerable, non-inherited property
     25 called `key`.  [For information on enumerability and ownership of properties, see
     26 the MDN
     27 documentation.](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Enumerability_and_ownership_of_properties)
     28 
     29 ### forEachKey(obj, callback)
     30 
     31 Like Array.forEach, but iterates enumerable, owned properties of an object
     32 rather than elements of an array.  Equivalent to:
     33 
     34     for (var key in obj) {
     35             if (Object.prototype.hasOwnProperty.call(obj, key)) {
     36                     callback(key, obj[key]);
     37             }
     38     }
     39 
     40 
     41 ### flattenObject(obj, depth)
     42 
     43 Flattens an object up to a given level of nesting, returning an array of arrays
     44 of length "depth + 1", where the first "depth" elements correspond to flattened
     45 columns and the last element contains the remaining object .  For example:
     46 
     47     flattenObject({
     48         'I': {
     49             'A': {
     50                 'i': {
     51                     'datum1': [ 1, 2 ],
     52                     'datum2': [ 3, 4 ]
     53                 },
     54                 'ii': {
     55                     'datum1': [ 3, 4 ]
     56                 }
     57             },
     58             'B': {
     59                 'i': {
     60                     'datum1': [ 5, 6 ]
     61                 },
     62                 'ii': {
     63                     'datum1': [ 7, 8 ],
     64                     'datum2': [ 3, 4 ],
     65                 },
     66                 'iii': {
     67                 }
     68             }
     69         },
     70         'II': {
     71             'A': {
     72                 'i': {
     73                     'datum1': [ 1, 2 ],
     74                     'datum2': [ 3, 4 ]
     75                 }
     76             }
     77         }
     78     }, 3)
     79 
     80 becomes:
     81 
     82     [
     83         [ 'I',  'A', 'i',   { 'datum1': [ 1, 2 ], 'datum2': [ 3, 4 ] } ],
     84         [ 'I',  'A', 'ii',  { 'datum1': [ 3, 4 ] } ],
     85         [ 'I',  'B', 'i',   { 'datum1': [ 5, 6 ] } ],
     86         [ 'I',  'B', 'ii',  { 'datum1': [ 7, 8 ], 'datum2': [ 3, 4 ] } ],
     87         [ 'I',  'B', 'iii', {} ],
     88         [ 'II', 'A', 'i',   { 'datum1': [ 1, 2 ], 'datum2': [ 3, 4 ] } ]
     89     ]
     90 
     91 This function is strict: "depth" must be a non-negative integer and "obj" must
     92 be a non-null object with at least "depth" levels of nesting under all keys.
     93 
     94 
     95 ### flattenIter(obj, depth, func)
     96 
     97 This is similar to `flattenObject` except that instead of returning an array,
     98 this function invokes `func(entry)` for each `entry` in the array that
     99 `flattenObject` would return.  `flattenIter(obj, depth, func)` is logically
    100 equivalent to `flattenObject(obj, depth).forEach(func)`.  Importantly, this
    101 version never constructs the full array.  Its memory usage is O(depth) rather
    102 than O(n) (where `n` is the number of flattened elements).
    103 
    104 There's another difference between `flattenObject` and `flattenIter` that's
    105 related to the special case where `depth === 0`.  In this case, `flattenObject`
    106 omits the array wrapping `obj` (which is regrettable).
    107 
    108 
    109 ### pluck(obj, key)
    110 
    111 Fetch nested property "key" from object "obj", traversing objects as needed.
    112 For example, `pluck(obj, "foo.bar.baz")` is roughly equivalent to
    113 `obj.foo.bar.baz`, except that:
    114 
    115 1. If traversal fails, the resulting value is undefined, and no error is
    116    thrown.  For example, `pluck({}, "foo.bar")` is just undefined.
    117 2. If "obj" has property "key" directly (without traversing), the
    118    corresponding property is returned.  For example,
    119    `pluck({ 'foo.bar': 1 }, 'foo.bar')` is 1, not undefined.  This is also
    120    true recursively, so `pluck({ 'a': { 'foo.bar': 1 } }, 'a.foo.bar')` is
    121    also 1, not undefined.
    122 
    123 
    124 ### randElt(array)
    125 
    126 Returns an element from "array" selected uniformly at random.  If "array" is
    127 empty, throws an Error.
    128 
    129 
    130 ### startsWith(str, prefix)
    131 
    132 Returns true if the given string starts with the given prefix and false
    133 otherwise.
    134 
    135 
    136 ### endsWith(str, suffix)
    137 
    138 Returns true if the given string ends with the given suffix and false
    139 otherwise.
    140 
    141 
    142 ### parseInteger(str, options)
    143 
    144 Parses the contents of `str` (a string) as an integer. On success, the integer
    145 value is returned (as a number). On failure, an error is **returned** describing
    146 why parsing failed.
    147 
    148 By default, leading and trailing whitespace characters are not allowed, nor are
    149 trailing characters that are not part of the numeric representation. This
    150 behaviour can be toggled by using the options below. The empty string (`''`) is
    151 not considered valid input. If the return value cannot be precisely represented
    152 as a number (i.e., is smaller than `Number.MIN_SAFE_INTEGER` or larger than
    153 `Number.MAX_SAFE_INTEGER`), an error is returned. Additionally, the string
    154 `'-0'` will be parsed as the integer `0`, instead of as the IEEE floating point
    155 value `-0`.
    156 
    157 This function accepts both upper and lowercase characters for digits, similar to
    158 `parseInt()`, `Number()`, and [strtol(3C)](https://illumos.org/man/3C/strtol).
    159 
    160 The following may be specified in `options`:
    161 
    162 Option             | Type    | Default | Meaning
    163 ------------------ | ------- | ------- | ---------------------------
    164 base               | number  | 10      | numeric base (radix) to use, in the range 2 to 36
    165 allowSign          | boolean | true    | whether to interpret any leading `+` (positive) and `-` (negative) characters
    166 allowImprecise     | boolean | false   | whether to accept values that may have lost precision (past `MAX_SAFE_INTEGER` or below `MIN_SAFE_INTEGER`)
    167 allowPrefix        | boolean | false   | whether to interpret the prefixes `0b` (base 2), `0o` (base 8), `0t` (base 10), or `0x` (base 16)
    168 allowTrailing      | boolean | false   | whether to ignore trailing characters
    169 trimWhitespace     | boolean | false   | whether to trim any leading or trailing whitespace/line terminators
    170 leadingZeroIsOctal | boolean | false   | whether a leading zero indicates octal
    171 
    172 Note that if `base` is unspecified, and `allowPrefix` or `leadingZeroIsOctal`
    173 are, then the leading characters can change the default base from 10. If `base`
    174 is explicitly specified and `allowPrefix` is true, then the prefix will only be
    175 accepted if it matches the specified base. `base` and `leadingZeroIsOctal`
    176 cannot be used together.
    177 
    178 **Context:** It's tricky to parse integers with JavaScript's built-in facilities
    179 for several reasons:
    180 
    181 - `parseInt()` and `Number()` by default allow the base to be specified in the
    182   input string by a prefix (e.g., `0x` for hex).
    183 - `parseInt()` allows trailing nonnumeric characters.
    184 - `Number(str)` returns 0 when `str` is the empty string (`''`).
    185 - Both functions return incorrect values when the input string represents a
    186   valid integer outside the range of integers that can be represented precisely.
    187   Specifically, `parseInt('9007199254740993')` returns 9007199254740992.
    188 - Both functions always accept `-` and `+` signs before the digit.
    189 - Some older JavaScript engines always interpret a leading 0 as indicating
    190   octal, which can be surprising when parsing input from users who expect a
    191   leading zero to be insignificant.
    192 
    193 While each of these may be desirable in some contexts, there are also times when
    194 none of them are wanted. `parseInteger()` grants greater control over what
    195 input's permissible.
    196 
    197 ### iso8601(date)
    198 
    199 Converts a Date object to an ISO8601 date string of the form
    200 "YYYY-MM-DDTHH:MM:SS.sssZ".  This format is not customizable.
    201 
    202 
    203 ### parseDateTime(str)
    204 
    205 Parses a date expressed as a string, as either a number of milliseconds since
    206 the epoch or any string format that Date accepts, giving preference to the
    207 former where these two sets overlap (e.g., strings containing small numbers).
    208 
    209 
    210 ### hrtimeDiff(timeA, timeB)
    211 
    212 Given two hrtime readings (as from Node's `process.hrtime()`), where timeA is
    213 later than timeB, compute the difference and return that as an hrtime.  It is
    214 illegal to invoke this for a pair of times where timeB is newer than timeA.
    215 
    216 ### hrtimeAdd(timeA, timeB)
    217 
    218 Add two hrtime intervals (as from Node's `process.hrtime()`), returning a new
    219 hrtime interval array.  This function does not modify either input argument.
    220 
    221 
    222 ### hrtimeAccum(timeA, timeB)
    223 
    224 Add two hrtime intervals (as from Node's `process.hrtime()`), storing the
    225 result in `timeA`.  This function overwrites (and returns) the first argument
    226 passed in.
    227 
    228 
    229 ### hrtimeNanosec(timeA), hrtimeMicrosec(timeA), hrtimeMillisec(timeA)
    230 
    231 This suite of functions converts a hrtime interval (as from Node's
    232 `process.hrtime()`) into a scalar number of nanoseconds, microseconds or
    233 milliseconds.  Results are truncated, as with `Math.floor()`.
    234 
    235 
    236 ### validateJsonObject(schema, object)
    237 
    238 Uses JSON validation (via JSV) to validate the given object against the given
    239 schema.  On success, returns null.  On failure, *returns* (does not throw) a
    240 useful Error object.
    241 
    242 
    243 ### extraProperties(object, allowed)
    244 
    245 Check an object for unexpected properties.  Accepts the object to check, and an
    246 array of allowed property name strings.  If extra properties are detected, an
    247 array of extra property names is returned.  If no properties other than those
    248 in the allowed list are present on the object, the returned array will be of
    249 zero length.
    250 
    251 ### mergeObjects(provided, overrides, defaults)
    252 
    253 Merge properties from objects "provided", "overrides", and "defaults".  The
    254 intended use case is for functions that accept named arguments in an "args"
    255 object, but want to provide some default values and override other values.  In
    256 that case, "provided" is what the caller specified, "overrides" are what the
    257 function wants to override, and "defaults" contains default values.
    258 
    259 The function starts with the values in "defaults", overrides them with the
    260 values in "provided", and then overrides those with the values in "overrides".
    261 For convenience, any of these objects may be falsey, in which case they will be
    262 ignored.  The input objects are never modified, but properties in the returned
    263 object are not deep-copied.
    264 
    265 For example:
    266 
    267     mergeObjects(undefined, { 'objectMode': true }, { 'highWaterMark': 0 })
    268 
    269 returns:
    270 
    271     { 'objectMode': true, 'highWaterMark': 0 }
    272 
    273 For another example:
    274 
    275     mergeObjects(
    276         { 'highWaterMark': 16, 'objectMode': 7 }, /* from caller */
    277         { 'objectMode': true },                   /* overrides */
    278         { 'highWaterMark': 0 });                  /* default */
    279 
    280 returns:
    281 
    282     { 'objectMode': true, 'highWaterMark': 16 }
    283 
    284 
    285 # Contributing
    286 
    287 See separate [contribution guidelines](CONTRIBUTING.md).