keycomfort 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/bundle.js +695 -401
  2. package/package.json +4 -5
package/dist/bundle.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- 'use strict';var require$$0$1=require('node:process'),require$$1=require('node:child_process'),require$$3=require('node:fs'),require$$2=require('node:path'),require$$4$1=require('node:readline'),require$$0=require('node:events'),require$$2$1=require('node:os'),require$$4=require('node:fs/promises'),require$$6$1=require('node:stream'),require$$7=require('node:assert');function getDefaultExportFromCjs (x) {
2
+ 'use strict';var require$$0$1=require('node:process'),require$$1=require('node:child_process'),require$$3=require('node:fs'),require$$3$1=require('node:path'),require$$4$1=require('node:readline'),require$$0=require('node:events'),require$$0$2=require('node:os'),require$$2=require('node:fs/promises'),require$$4=require('node:stream'),require$$7=require('node:assert');function getDefaultExportFromCjs (x) {
3
3
  return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
4
4
  }
5
5
 
@@ -1485,7 +1485,7 @@ function requireCommand () {
1485
1485
  hasRequiredCommand = 1;
1486
1486
  const EventEmitter = require$$0.EventEmitter;
1487
1487
  const childProcess = require$$1;
1488
- const path = require$$2;
1488
+ const path = require$$3$1;
1489
1489
  const fs = require$$3;
1490
1490
  const process = require$$0$1;
1491
1491
 
@@ -4955,7 +4955,7 @@ function toJS(value, arg, ctx) {
4955
4955
  data = anchors.get(source);
4956
4956
  }
4957
4957
  /* istanbul ignore if */
4958
- if (!data || data.res === undefined) {
4958
+ if (data?.res === undefined) {
4959
4959
  const msg = 'This should not happen: Alias anchor was not resolved?';
4960
4960
  throw new ReferenceError(msg);
4961
4961
  }
@@ -5951,7 +5951,7 @@ function stringify$2(item, ctx, onComment, onChompKeep) {
5951
5951
  ws += `\n${indentComment(cs, ctx.indent)}`;
5952
5952
  }
5953
5953
  if (valueStr === '' && !ctx.inFlow) {
5954
- if (ws === '\n')
5954
+ if (ws === '\n' && valueComment)
5955
5955
  ws = '\n\n';
5956
5956
  }
5957
5957
  else {
@@ -6573,7 +6573,7 @@ function asItemIndex(key) {
6573
6573
  const num = typeof value === 'number' ? value : Number(value);
6574
6574
  if (!isFinite(num))
6575
6575
  return isNaN(num) ? '.nan' : num < 0 ? '-.inf' : '.inf';
6576
- let n = JSON.stringify(value);
6576
+ let n = Object.is(value, -0) ? '-0' : JSON.stringify(value);
6577
6577
  if (!format &&
6578
6578
  minFractionDigits &&
6579
6579
  (!tag || tag === 'tag:yaml.org,2002:float') &&
@@ -7793,7 +7793,7 @@ const prettifyError = (src, lc) => (error) => {
7793
7793
  if (/[^ ]/.test(lineStr)) {
7794
7794
  let count = 1;
7795
7795
  const end = error.linePos[1];
7796
- if (end && end.line === line && end.col > col) {
7796
+ if (end?.line === line && end.col > col) {
7797
7797
  count = Math.max(1, Math.min(end.col - col, 80 - ci));
7798
7798
  }
7799
7799
  const pointer = ' '.repeat(ci) + '^'.repeat(count);
@@ -8116,7 +8116,7 @@ function resolveBlockMap({ composeNode, composeEmptyNode }, ctx, bm, onError, ta
8116
8116
  });
8117
8117
  if (!props.found) {
8118
8118
  if (props.anchor || props.tag || value) {
8119
- if (value && value.type === 'block-seq')
8119
+ if (value?.type === 'block-seq')
8120
8120
  onError(props.end, 'BAD_INDENT', 'All sequence items must start at the same column');
8121
8121
  else
8122
8122
  onError(offset, 'MISSING_CHAR', 'Sequence item without - indicator');
@@ -8298,7 +8298,7 @@ function resolveFlowCollection({ composeNode, composeEmptyNode }, ctx, fc, onErr
8298
8298
  }
8299
8299
  }
8300
8300
  else if (value) {
8301
- if ('source' in value && value.source && value.source[0] === ':')
8301
+ if ('source' in value && value.source?.[0] === ':')
8302
8302
  onError(value, 'MISSING_CHAR', `Missing space after : in ${fcName}`);
8303
8303
  else
8304
8304
  onError(valueProps.start, 'MISSING_CHAR', `Missing , or : between ${fcName} items`);
@@ -8342,7 +8342,7 @@ function resolveFlowCollection({ composeNode, composeEmptyNode }, ctx, fc, onErr
8342
8342
  const expectedEnd = isMap ? '}' : ']';
8343
8343
  const [ce, ...ee] = fc.end;
8344
8344
  let cePos = offset;
8345
- if (ce && ce.source === expectedEnd)
8345
+ if (ce?.source === expectedEnd)
8346
8346
  cePos = ce.offset + ce.source.length;
8347
8347
  else {
8348
8348
  const name = fcName[0].toUpperCase() + fcName.substring(1);
@@ -8420,7 +8420,7 @@ function composeCollection(CN, ctx, token, props, onError) {
8420
8420
  let tag = ctx.schema.tags.find(t => t.tag === tagName && t.collection === expType);
8421
8421
  if (!tag) {
8422
8422
  const kt = ctx.schema.knownTags[tagName];
8423
- if (kt && kt.collection === expType) {
8423
+ if (kt?.collection === expType) {
8424
8424
  ctx.schema.tags.push(Object.assign({}, kt, { default: false }));
8425
8425
  tag = kt;
8426
8426
  }
@@ -10711,7 +10711,7 @@ class Parser {
10711
10711
  }
10712
10712
  *step() {
10713
10713
  const top = this.peek(1);
10714
- if (this.type === 'doc-end' && (!top || top.type !== 'doc-end')) {
10714
+ if (this.type === 'doc-end' && top?.type !== 'doc-end') {
10715
10715
  while (this.stack.length > 0)
10716
10716
  yield* this.pop();
10717
10717
  this.stack.push({
@@ -11243,7 +11243,7 @@ class Parser {
11243
11243
  do {
11244
11244
  yield* this.pop();
11245
11245
  top = this.peek(1);
11246
- } while (top && top.type === 'flow-collection');
11246
+ } while (top?.type === 'flow-collection');
11247
11247
  }
11248
11248
  else if (fc.end.length === 0) {
11249
11249
  switch (this.type) {
@@ -11540,15 +11540,210 @@ function stringify(value, replacer, options) {
11540
11540
  return new Document(value, _replacer, options).toString(options);
11541
11541
  }var YAML=/*#__PURE__*/Object.freeze({__proto__:null,Alias:Alias,CST:cst,Composer:Composer,Document:Document,Lexer:Lexer,LineCounter:LineCounter,Pair:Pair,Parser:Parser,Scalar:Scalar,Schema:Schema,YAMLError:YAMLError,YAMLMap:YAMLMap,YAMLParseError:YAMLParseError,YAMLSeq:YAMLSeq,YAMLWarning:YAMLWarning,isAlias:isAlias,isCollection:isCollection$1,isDocument:isDocument,isMap:isMap,isNode:isNode,isPair:isPair,isScalar:isScalar$1,isSeq:isSeq,parse:parse,parseAllDocuments:parseAllDocuments,parseDocument:parseDocument,stringify:stringify,visit:visit$1,visitAsync:visitAsync});// `export * as default from ...` fails on Webpack v4
11542
11542
  // https://github.com/eemeli/yaml/issues/228
11543
- var browser=/*#__PURE__*/Object.freeze({__proto__:null,Alias:Alias,CST:cst,Composer:Composer,Document:Document,Lexer:Lexer,LineCounter:LineCounter,Pair:Pair,Parser:Parser,Scalar:Scalar,Schema:Schema,YAMLError:YAMLError,YAMLMap:YAMLMap,YAMLParseError:YAMLParseError,YAMLSeq:YAMLSeq,YAMLWarning:YAMLWarning,default:YAML,isAlias:isAlias,isCollection:isCollection$1,isDocument:isDocument,isMap:isMap,isNode:isNode,isPair:isPair,isScalar:isScalar$1,isSeq:isSeq,parse:parse,parseAllDocuments:parseAllDocuments,parseDocument:parseDocument,stringify:stringify,visit:visit$1,visitAsync:visitAsync});var require$$6 = /*@__PURE__*/getAugmentedNamespace(browser);var bundle$2 = {};var hasRequiredBundle$2;
11543
+ var browser=/*#__PURE__*/Object.freeze({__proto__:null,Alias:Alias,CST:cst,Composer:Composer,Document:Document,Lexer:Lexer,LineCounter:LineCounter,Pair:Pair,Parser:Parser,Scalar:Scalar,Schema:Schema,YAMLError:YAMLError,YAMLMap:YAMLMap,YAMLParseError:YAMLParseError,YAMLSeq:YAMLSeq,YAMLWarning:YAMLWarning,default:YAML,isAlias:isAlias,isCollection:isCollection$1,isDocument:isDocument,isMap:isMap,isNode:isNode,isPair:isPair,isScalar:isScalar$1,isSeq:isSeq,parse:parse,parseAllDocuments:parseAllDocuments,parseDocument:parseDocument,stringify:stringify,visit:visit$1,visitAsync:visitAsync});var require$$6 = /*@__PURE__*/getAugmentedNamespace(browser);var amekusa_util = {};var hasRequiredAmekusa_util;
11544
11544
 
11545
- function requireBundle$2 () {
11546
- if (hasRequiredBundle$2) return bundle$2;
11547
- hasRequiredBundle$2 = 1;
11545
+ function requireAmekusa_util () {
11546
+ if (hasRequiredAmekusa_util) return amekusa_util;
11547
+ hasRequiredAmekusa_util = 1;
11548
+ var os=require$$0$2,fs=require$$3,fsp=require$$2,path=require$$3$1,node_stream=require$$4,node_process=require$$0$1,node_child_process=require$$1,assert=require$$7;function _interopNamespaceDefault(e){var n=Object.create(null);if(e){Object.keys(e).forEach(function(k){if(k!=='default'){var d=Object.getOwnPropertyDescriptor(e,k);Object.defineProperty(n,k,d.get?d:{enumerable:true,get:function(){return e[k]}});}});}n.default=e;return Object.freeze(n)}var fsp__namespace=/*#__PURE__*/_interopNamespaceDefault(fsp);/*!
11549
+ * === @amekusa/util.js/gen === *
11550
+ * MIT License
11551
+ *
11552
+ * Copyright (c) 2024 Satoshi Soma
11553
+ *
11554
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
11555
+ * of this software and associated documentation files (the "Software"), to deal
11556
+ * in the Software without restriction, including without limitation the rights
11557
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11558
+ * copies of the Software, and to permit persons to whom the Software is
11559
+ * furnished to do so, subject to the following conditions:
11560
+ *
11561
+ * The above copyright notice and this permission notice shall be included in all
11562
+ * copies or substantial portions of the Software.
11563
+ *
11564
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
11565
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
11566
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
11567
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
11568
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
11569
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
11570
+ * SOFTWARE.
11571
+ */
11548
11572
 
11549
- Object.defineProperty(bundle$2, '__esModule', { value: true });
11573
+ /**
11574
+ * Coerces the given value into an array.
11575
+ * @param {any} x
11576
+ * @return {any[]}
11577
+ */
11578
+ function arr(x) {
11579
+ return Array.isArray(x) ? x : [x];
11580
+ }
11550
11581
 
11551
- /*!
11582
+ /**
11583
+ * Checks the type of the given value matches with one of the given types.
11584
+ * If a constructor is given to `types`, it checks if `x` is `instanceof` the constructor.
11585
+ * @param {any} x
11586
+ * @param {...string|function} types - Type or Constructor
11587
+ * @return {boolean}
11588
+ */
11589
+ function is(x, ...types) {
11590
+ let t = typeof x;
11591
+ for (let i = 0; i < types.length; i++) {
11592
+ let v = types[i];
11593
+ if (typeof v == 'string') {
11594
+ if (v == 'array') {
11595
+ if (Array.isArray(x)) return true;
11596
+ } else if (t == v) return true;
11597
+ } else if (x instanceof v) return true;
11598
+ }
11599
+ return false;
11600
+ }
11601
+
11602
+ /**
11603
+ * Returns whether the given value can be considered as "empty".
11604
+ * @param {any} x
11605
+ * @return {boolean}
11606
+ */
11607
+ function isEmpty(x) {
11608
+ if (Array.isArray(x)) return x.length == 0;
11609
+ switch (typeof x) {
11610
+ case 'string':
11611
+ return !x;
11612
+ case 'object':
11613
+ for (let _ in x) return false;
11614
+ return true;
11615
+ case 'undefined':
11616
+ return true;
11617
+ }
11618
+ return false;
11619
+ }
11620
+
11621
+ /**
11622
+ * Returns whether the given value can be considered as "empty" or "falsy".
11623
+ * Faster than {@link isEmpty}.
11624
+ * @param {any} x
11625
+ * @return {boolean}
11626
+ */
11627
+ function isEmptyOrFalsy(x) {
11628
+ if (!x) return true;
11629
+ if (Array.isArray(x)) return x.length == 0;
11630
+ if (typeof x == 'object') {
11631
+ for (let _ in x) return false;
11632
+ }
11633
+ return false;
11634
+ }
11635
+
11636
+ /**
11637
+ * @function isEmptyOrFalsey
11638
+ * Alias of {@link isEmptyOrFalsy}.
11639
+ */
11640
+ const isEmptyOrFalsey = isEmptyOrFalsy;
11641
+
11642
+ /**
11643
+ * Removes "empty" values from the given object or array.
11644
+ * @param {object|any[]} x
11645
+ * @param {number} recurse - Recursion limit
11646
+ * @return {object|any[]} modified `x`
11647
+ */
11648
+ function clean$1(x, recurse = 8) {
11649
+ if (recurse) {
11650
+ if (Array.isArray(x)) {
11651
+ let r = [];
11652
+ for (let i = 0; i < x.length; i++) {
11653
+ let v = clean$1(x[i], recurse - 1);
11654
+ if (!isEmpty(v)) r.push(v);
11655
+ }
11656
+ return r;
11657
+ }
11658
+ if (typeof x == 'object') {
11659
+ let r = {};
11660
+ for (let k in x) {
11661
+ let v = clean$1(x[k], recurse - 1);
11662
+ if (!isEmpty(v)) r[k] = v;
11663
+ }
11664
+ return r;
11665
+ }
11666
+ }
11667
+ return x;
11668
+ }
11669
+
11670
+ /**
11671
+ * Merges the 2nd object into the 1st object recursively (deep-merge). The 1st object will be modified.
11672
+ * @param {object} x - The 1st object
11673
+ * @param {object} y - The 2nd object
11674
+ * @param {object} [opts] - Options
11675
+ * @param {number} opts.recurse=8 - Recurstion limit. Negative number means unlimited
11676
+ * @param {boolean|string} opts.mergeArrays - How to merge arrays
11677
+ * - `true`: merge x with y
11678
+ * - 'push': push y elements to x
11679
+ * - 'concat': concat x and y
11680
+ * - other: replace x with y
11681
+ * @return {object} The 1st object
11682
+ */
11683
+ function merge$1(x, y, opts = {}) {
11684
+ if (!('recurse' in opts)) opts.recurse = 8;
11685
+ switch (Array.isArray(x) + Array.isArray(y)) {
11686
+ case 0: // no array
11687
+ if (opts.recurse && x && y && typeof x == 'object' && typeof y == 'object') {
11688
+ opts.recurse--;
11689
+ for (let k in y) x[k] = merge$1(x[k], y[k], opts);
11690
+ opts.recurse++;
11691
+ return x;
11692
+ }
11693
+ case 1: // 1 array
11694
+ return y;
11695
+ }
11696
+ // 2 arrays
11697
+ switch (opts.mergeArrays) {
11698
+ case true:
11699
+ for (let i = 0; i < y.length; i++) {
11700
+ if (!x.includes(y[i])) x.push(y[i]);
11701
+ }
11702
+ return x;
11703
+ case 'push':
11704
+ x.push(...y);
11705
+ return x;
11706
+ case 'concat':
11707
+ return x.concat(y);
11708
+ }
11709
+ return y;
11710
+ }
11711
+
11712
+ /**
11713
+ * Gets a property from the given object by the given string path.
11714
+ * @param {object} obj - Object to traverse
11715
+ * @param {string} path - Property names separated with '.'
11716
+ * @return {any} value of the found property, or undefined if it's not found
11717
+ */
11718
+ function dig(obj, path) {
11719
+ path = path.split('.');
11720
+ for (let i = 0; i < path.length; i++) {
11721
+ let p = path[i];
11722
+ if (typeof obj == 'object' && p in obj) obj = obj[p];
11723
+ else return undefined;
11724
+ }
11725
+ return obj;
11726
+ }
11727
+
11728
+ /**
11729
+ * Substitutes the properties of the given data for the references in the given string.
11730
+ * @param {string} str - String that contains references to the properties
11731
+ * @param {object} data - Object that contains properties to replace the references
11732
+ * @param {object} [opts] - Options
11733
+ * @return {string} a modified `str`
11734
+ */
11735
+ function subst(str, data, opts = {}) {
11736
+ let {
11737
+ modifier = null,
11738
+ start = '{{',
11739
+ end = '}}',
11740
+ } = opts;
11741
+ let ref = new RegExp(start + '\\s*([-.\\w]+)\\s*' + end, 'g');
11742
+ return str.replaceAll(ref, modifier
11743
+ ? (_, m1) => (modifier(dig(data, m1), m1, data) || '')
11744
+ : (_, m1) => (dig(data, m1) || '')
11745
+ );
11746
+ }var gen=/*#__PURE__*/Object.freeze({__proto__:null,arr:arr,clean:clean$1,dig:dig,is:is,isEmpty:isEmpty,isEmptyOrFalsey:isEmptyOrFalsey,isEmptyOrFalsy:isEmptyOrFalsy,merge:merge$1,subst:subst});/*!
11552
11747
  * === @amekusa/util.js/web === *
11553
11748
  * MIT License
11554
11749
  *
@@ -11597,15 +11792,7 @@ function requireBundle$2 () {
11597
11792
  // - This avoids double-escaping '&' symbols
11598
11793
  // - Regex negative match: (?!word)
11599
11794
 
11600
- const escHTML_replace = found => `&${escHTML_map[found]};`;
11601
-
11602
- var web = /*#__PURE__*/Object.freeze({
11603
- __proto__: null,
11604
- escHTML: escHTML,
11605
- escHtml: escHtml
11606
- });
11607
-
11608
- /*!
11795
+ const escHTML_replace = found => `&${escHTML_map[found]};`;var web=/*#__PURE__*/Object.freeze({__proto__:null,escHTML:escHTML,escHtml:escHtml});/*!
11609
11796
  * === @amekusa/util.js/time === *
11610
11797
  * MIT License
11611
11798
  *
@@ -11745,250 +11932,59 @@ function requireBundle$2 () {
11745
11932
  * - If omited, the return value will be an array consists of the three parts.
11746
11933
  * - If a string is passed, the three parts will be joined with the string as a separator.
11747
11934
  * - If an object is passed, the three parts will be assigned as `h`, `m`, and `s` properties.
11748
- * @return {string|string[]|object}
11749
- */
11750
- function hms(d, format = null) {
11751
- let r = [
11752
- d.getHours().toString().padStart(2, '0'),
11753
- d.getMinutes().toString().padStart(2, '0'),
11754
- d.getSeconds().toString().padStart(2, '0'),
11755
- ];
11756
- switch (typeof format) {
11757
- case 'string':
11758
- return r.join(format);
11759
- case 'object':
11760
- if (!format) return r;
11761
- format.h = r[0];
11762
- format.m = r[1];
11763
- format.s = r[2];
11764
- return format;
11765
- default:
11766
- if (!format) return r;
11767
- throw `invalid type`;
11768
- }
11769
- }
11770
-
11771
- /**
11772
- * Returns a string representation of the given `Date` in ISO 9075 format, which is standard for MySQL.
11773
- * @param {Date} d - Date object
11774
- * @return {string} a string like `YYYY-MM-DD hh:mm:ss`
11775
- */
11776
- function iso9075(d) {
11777
- return ymd(d, '-') + ' ' + hms(d, ':');
11778
- }
11779
-
11780
- var time = /*#__PURE__*/Object.freeze({
11781
- __proto__: null,
11782
- addTime: addTime,
11783
- ceil: ceil,
11784
- date: date,
11785
- floor: floor,
11786
- hms: hms,
11787
- iso9075: iso9075,
11788
- localize: localize,
11789
- ms: ms,
11790
- quantize: quantize,
11791
- round: round,
11792
- ymd: ymd
11793
- });
11794
-
11795
- /*!
11796
- * === @amekusa/util.js === *
11797
- * MIT License
11798
- *
11799
- * Copyright (c) 2024 Satoshi Soma
11800
- *
11801
- * Permission is hereby granted, free of charge, to any person obtaining a copy
11802
- * of this software and associated documentation files (the "Software"), to deal
11803
- * in the Software without restriction, including without limitation the rights
11804
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11805
- * copies of the Software, and to permit persons to whom the Software is
11806
- * furnished to do so, subject to the following conditions:
11807
- *
11808
- * The above copyright notice and this permission notice shall be included in all
11809
- * copies or substantial portions of the Software.
11810
- *
11811
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
11812
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
11813
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
11814
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
11815
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
11816
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
11817
- * SOFTWARE.
11818
- */
11819
-
11820
- /**
11821
- * Coerces the given value into an array.
11822
- * @param {any} x
11823
- * @return {any[]}
11824
- */
11825
- function arr(x) {
11826
- return Array.isArray(x) ? x : [x];
11827
- }
11828
-
11829
- /**
11830
- * Alias of `Array.isArray`.
11831
- * @return {boolean}
11832
- */
11833
- const isArray = Array.isArray;
11834
-
11835
- /**
11836
- * Returns whether the given value is a number or a string.
11837
- * @param {any} x
11838
- * @return {boolean}
11839
- */
11840
- function isNumOrStr(x) {
11841
- switch (typeof x) {
11842
- case 'number':
11843
- case 'string':
11844
- return true;
11845
- }
11846
- return false;
11847
- }
11848
-
11849
- /**
11850
- * Returns whether the given value can be considered as "empty".
11851
- * @param {any} x
11852
- * @return {boolean}
11853
- */
11854
- function isEmpty(x) {
11855
- if (Array.isArray(x)) return x.length == 0;
11856
- switch (typeof x) {
11857
- case 'string':
11858
- return !x;
11859
- case 'object':
11860
- if (x === null) return true;
11861
- for (let i in x) return false;
11862
- case 'undefined':
11863
- return true;
11864
- }
11865
- return false;
11866
- }
11867
-
11868
- /**
11869
- * Removes "empty" values from the given object or array.
11870
- * @param {object|any[]} x
11871
- * @param {number} recurse - Recursion limit
11872
- * @return {object|any[]} modified `x`
11873
- */
11874
- function clean(x, recurse = 8) {
11875
- if (recurse) {
11876
- if (Array.isArray(x)) {
11877
- let r = [];
11878
- for (let i = 0; i < x.length; i++) {
11879
- let I = clean(x[i], recurse - 1);
11880
- if (!isEmpty(I)) r.push(I);
11881
- }
11882
- return r;
11883
- }
11884
- if (typeof x == 'object') {
11885
- let r = {};
11886
- for (let k in x) {
11887
- let v = clean(x[k], recurse - 1);
11888
- if (!isEmpty(v)) r[k] = v;
11889
- }
11890
- return r;
11891
- }
11892
- }
11893
- return x;
11894
- }
11895
-
11896
- /**
11897
- * Merges the 2nd object into the 1st object recursively (deep-merge). The 1st object will be modified.
11898
- * @param {object} x - The 1st object
11899
- * @param {object} y - The 2nd object
11900
- * @param {object} [opts] - Options
11901
- * @param {number} opts.recurse=8 - Recurstion limit. Negative number means unlimited
11902
- * @param {boolean|string} opts.mergeArrays - How to merge arrays
11903
- * - `true`: merge x with y
11904
- * - 'push': push y elements to x
11905
- * - 'concat': concat x and y
11906
- * - other: replace x with y
11907
- * @return {object} The 1st object
11908
- */
11909
- function merge(x, y, opts = {}) {
11910
- if (!('recurse' in opts)) opts.recurse = 8;
11911
- switch (Array.isArray(x) + Array.isArray(y)) {
11912
- case 0: // no array
11913
- if (opts.recurse && x && y && typeof x == 'object' && typeof y == 'object') {
11914
- opts.recurse--;
11915
- for (let k in y) x[k] = merge(x[k], y[k], opts);
11916
- opts.recurse++;
11917
- return x;
11918
- }
11919
- case 1: // 1 array
11920
- return y;
11921
- }
11922
- // 2 arrays
11923
- switch (opts.mergeArrays) {
11924
- case true:
11925
- for (let i = 0; i < y.length; i++) {
11926
- if (!x.includes(y[i])) x.push(y[i]);
11927
- }
11928
- return x;
11929
- case 'push':
11930
- x.push(...y);
11931
- return x;
11932
- case 'concat':
11933
- return x.concat(y);
11934
- }
11935
- return y;
11936
- }
11937
-
11938
- var main = {
11939
- arr,
11940
- isEmpty,
11941
- clean,
11942
- merge,
11943
- };
11944
-
11945
- bundle$2.arr = arr;
11946
- bundle$2.clean = clean;
11947
- bundle$2.default = main;
11948
- bundle$2.isArray = isArray;
11949
- bundle$2.isEmpty = isEmpty;
11950
- bundle$2.isNumOrStr = isNumOrStr;
11951
- bundle$2.merge = merge;
11952
- bundle$2.time = time;
11953
- bundle$2.web = web;
11954
- return bundle$2;
11955
- }var bundle$1 = {};var hasRequiredBundle$1;
11956
-
11957
- function requireBundle$1 () {
11958
- if (hasRequiredBundle$1) return bundle$1;
11959
- hasRequiredBundle$1 = 1;
11960
-
11961
- var node_process = require$$0$1;
11962
- var node_child_process = require$$1;
11963
- var os = require$$2$1;
11964
- var fs = require$$3;
11965
- var fsp = require$$4;
11966
- var path = require$$2;
11967
- var node_stream = require$$6$1;
11968
- var assert = require$$7;
11969
-
11970
- function _interopNamespaceDefault(e) {
11971
- var n = Object.create(null);
11972
- if (e) {
11973
- Object.keys(e).forEach(function (k) {
11974
- if (k !== 'default') {
11975
- var d = Object.getOwnPropertyDescriptor(e, k);
11976
- Object.defineProperty(n, k, d.get ? d : {
11977
- enumerable: true,
11978
- get: function () { return e[k]; }
11979
- });
11980
- }
11981
- });
11982
- }
11983
- n.default = e;
11984
- return Object.freeze(n);
11935
+ * @return {string|string[]|object}
11936
+ */
11937
+ function hms(d, format = null) {
11938
+ let r = [
11939
+ d.getHours().toString().padStart(2, '0'),
11940
+ d.getMinutes().toString().padStart(2, '0'),
11941
+ d.getSeconds().toString().padStart(2, '0'),
11942
+ ];
11943
+ switch (typeof format) {
11944
+ case 'string':
11945
+ return r.join(format);
11946
+ case 'object':
11947
+ if (!format) return r;
11948
+ format.h = r[0];
11949
+ format.m = r[1];
11950
+ format.s = r[2];
11951
+ return format;
11952
+ default:
11953
+ if (!format) return r;
11954
+ throw `invalid type`;
11955
+ }
11985
11956
  }
11986
11957
 
11987
- var fsp__namespace = /*#__PURE__*/_interopNamespaceDefault(fsp);
11988
-
11989
- /*!
11990
- * Shell Utils
11991
- * @author amekusa
11958
+ /**
11959
+ * Returns a string representation of the given `Date` in ISO 9075 format, which is standard for MySQL.
11960
+ * @param {Date} d - Date object
11961
+ * @return {string} a string like `YYYY-MM-DD hh:mm:ss`
11962
+ */
11963
+ function iso9075(d) {
11964
+ return ymd(d, '-') + ' ' + hms(d, ':');
11965
+ }var time=/*#__PURE__*/Object.freeze({__proto__:null,addTime:addTime,ceil:ceil,date:date,floor:floor,hms:hms,iso9075:iso9075,localize:localize,ms:ms,quantize:quantize,round:round,ymd:ymd});/*!
11966
+ * === @amekusa/util.js/sh === *
11967
+ * MIT License
11968
+ *
11969
+ * Copyright (c) 2024 Satoshi Soma
11970
+ *
11971
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
11972
+ * of this software and associated documentation files (the "Software"), to deal
11973
+ * in the Software without restriction, including without limitation the rights
11974
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11975
+ * copies of the Software, and to permit persons to whom the Software is
11976
+ * furnished to do so, subject to the following conditions:
11977
+ *
11978
+ * The above copyright notice and this permission notice shall be included in all
11979
+ * copies or substantial portions of the Software.
11980
+ *
11981
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
11982
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
11983
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
11984
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
11985
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
11986
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
11987
+ * SOFTWARE.
11992
11988
  */
11993
11989
 
11994
11990
  /**
@@ -12064,32 +12060,246 @@ function requireBundle$1 () {
12064
12060
  let value = 'development';
12065
12061
  if (set != undefined) node_process.env.NODE_ENV = set ? value : '';
12066
12062
  return node_process.env.NODE_ENV == value;
12067
- }
12068
-
12069
- var sh = /*#__PURE__*/Object.freeze({
12070
- __proto__: null,
12071
- args: args,
12072
- dev: dev,
12073
- exec: exec,
12074
- prod: prod
12075
- });
12076
-
12077
- /*!
12078
- * I/O Utils
12079
- * @author amekusa
12063
+ }var sh=/*#__PURE__*/Object.freeze({__proto__:null,args:args,dev:dev,exec:exec,prod:prod});/*!
12064
+ * === @amekusa/util.js/io/AssetImporter === *
12065
+ * MIT License
12066
+ *
12067
+ * Copyright (c) 2024 Satoshi Soma
12068
+ *
12069
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
12070
+ * of this software and associated documentation files (the "Software"), to deal
12071
+ * in the Software without restriction, including without limitation the rights
12072
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12073
+ * copies of the Software, and to permit persons to whom the Software is
12074
+ * furnished to do so, subject to the following conditions:
12075
+ *
12076
+ * The above copyright notice and this permission notice shall be included in all
12077
+ * copies or substantial portions of the Software.
12078
+ *
12079
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
12080
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
12081
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
12082
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
12083
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
12084
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
12085
+ * SOFTWARE.
12080
12086
  */
12081
12087
 
12082
12088
  /**
12089
+ * This is for copying styles or scripts to a certain HTML directory.
12090
+ * @author Satoshi Soma (github.com/amekusa)
12091
+ */
12092
+ class AssetImporter {
12093
+ /**
12094
+ * @param {object} config
12095
+ * @param {boolean} [config.minify=false] - Prefer `*.min.*` version
12096
+ * @param {string} config.src - Source dir to search
12097
+ * @param {string} config.dst - Destination dir
12098
+ */
12099
+ constructor(config) {
12100
+ this.config = Object.assign({
12101
+ minify: false,
12102
+ src: '', // source dir to search
12103
+ dst: '', // destination dir
12104
+ }, config);
12105
+ this.queue = [];
12106
+ this.results = {
12107
+ script: [],
12108
+ style: [],
12109
+ asset: [],
12110
+ };
12111
+ }
12112
+ /**
12113
+ * Adds a new item to import.
12114
+ * @param {string|string[]|object|object[]} newImport
12115
+ */
12116
+ add(newImport) {
12117
+ if (!Array.isArray(newImport)) newImport = [newImport];
12118
+ for (let i = 0; i < newImport.length; i++) {
12119
+ let item = newImport[i];
12120
+ switch (typeof item) {
12121
+ case 'string':
12122
+ item = {src: item};
12123
+ break;
12124
+ case 'object':
12125
+ if (Array.isArray(item)) throw `invalid type: array`;
12126
+ break;
12127
+ default:
12128
+ throw `invalid type: ${typeof item}`;
12129
+ }
12130
+ if (!('src' in item)) throw `'src' property is missing`;
12131
+ this.queue.push(Object.assign({
12132
+ order: 0,
12133
+ resolve: 'local',
12134
+ private: false,
12135
+ }, item));
12136
+ }
12137
+ }
12138
+ /**
12139
+ * Resolves the location of the given file path
12140
+ * @param {string} file - File path
12141
+ * @param {string} method - Resolution method
12142
+ * @return {string} Resolved file path
12143
+ */
12144
+ resolve(file, method) {
12145
+ let find = [];
12146
+ if (this.config.minify) {
12147
+ let _ext = ext(file);
12148
+ find.push(ext(file, '.min' + _ext));
12149
+ }
12150
+ find.push(file);
12151
+ for (let i = 0; i < find.length; i++) {
12152
+ let r;
12153
+ switch (method) {
12154
+ case 'require':
12155
+ try {
12156
+ r = require.resolve(find[i]);
12157
+ } catch (e) {
12158
+ if (e.code == 'MODULE_NOT_FOUND') continue;
12159
+ throw e;
12160
+ }
12161
+ return r;
12162
+ case 'local':
12163
+ r = path.join(this.config.src, find[i]);
12164
+ if (fs.existsSync(r)) return r;
12165
+ break;
12166
+ case 'local:absolute':
12167
+ case 'local:abs':
12168
+ r = find[i];
12169
+ if (fs.existsSync(r)) return r;
12170
+ break;
12171
+ default:
12172
+ throw `invalid resolution method: ${method}`;
12173
+ }
12174
+ }
12175
+ throw `cannot resolve '${file}'`;
12176
+ }
12177
+ /**
12178
+ * Imports all items in the queue at once.
12179
+ * @return {Promise}
12180
+ */
12181
+ import() {
12182
+ let tasks = [];
12183
+ let typeMap = {
12184
+ '.css': 'style',
12185
+ '.js': 'script',
12186
+ };
12187
+ this.queue.sort((a, b) => (Number(a.order) - Number(b.order))); // sort by order
12188
+ while (this.queue.length) {
12189
+ let item = this.queue.shift();
12190
+ let {type, src} = item;
12191
+ let url;
12192
+
12193
+ if (!item.resolve) { // no resolution
12194
+ url = src;
12195
+ if (!type) type = typeMap[ext(src)] || 'asset';
12196
+ console.log('---- File Link ----');
12197
+ console.log(' type:', type);
12198
+ console.log(' src:', src);
12199
+
12200
+ } else { // needs resolution
12201
+ let {dst:dstDir, as:dstFile} = item;
12202
+ let create = item.resolve == 'create'; // needs creation?
12203
+ if (create) {
12204
+ if (!dstFile) throw `'as' property is required with {resolve: 'create'}`;
12205
+ } else {
12206
+ src = this.resolve(src, item.resolve);
12207
+ if (!dstFile) dstFile = path.basename(src);
12208
+ }
12209
+ if (!type) type = typeMap[ext(dstFile)] || 'asset';
12210
+ if (!dstDir) dstDir = type + 's';
12211
+
12212
+ // absolute destination
12213
+ url = path.join(dstDir, dstFile);
12214
+ let dst = path.join(this.config.dst, url);
12215
+ dstDir = path.dirname(dst);
12216
+ if (!fs.existsSync(dstDir)) fs.mkdirSync(dstDir, {recursive:true});
12217
+
12218
+ // create/copy file
12219
+ if (create) {
12220
+ console.log('---- File Creation ----');
12221
+ console.log(' type:', type);
12222
+ console.log(' dst:', dst);
12223
+ tasks.push(fsp.writeFile(dst, src));
12224
+ } else {
12225
+ console.log('---- File Import ----');
12226
+ console.log(' type:', type);
12227
+ console.log(' src:', src);
12228
+ console.log(' dst:', dst);
12229
+ tasks.push(fsp.copyFile(src, dst));
12230
+ }
12231
+ }
12232
+
12233
+ if (!item.private) {
12234
+ if (!(type in this.results)) this.results[type] = [];
12235
+ this.results[type].push({type, url});
12236
+ }
12237
+ }
12238
+
12239
+ return tasks.length ? Promise.all(tasks) : Promise.resolve();
12240
+ }
12241
+ /**
12242
+ * Outputs HTML tags for imported items.
12243
+ * @param {string} [type] - Type
12244
+ * @return {string} HTML
12245
+ */
12246
+ toHTML(type = null) {
12247
+ let r;
12248
+ if (type) {
12249
+ let tmpl = templates[type];
12250
+ if (!tmpl) return '';
12251
+ if (Array.isArray(tmpl)) tmpl = tmpl.join('\n');
12252
+ let items = this.results[type];
12253
+ r = new Array(items.length);
12254
+ for (let i = 0; i < items.length; i++) {
12255
+ r[i] = tmpl.replaceAll('%s', items[i].url || '');
12256
+ }
12257
+ } else {
12258
+ let keys = Object.keys(this.results);
12259
+ r = new Array(keys.length);
12260
+ for (let i = 0; i < keys.length; i++) {
12261
+ r[i] = this.toHTML(keys[i]);
12262
+ }
12263
+ }
12264
+ return r.join('\n');
12265
+ }
12266
+ }
12267
+
12268
+ const templates = {
12269
+ script: [
12270
+ `<script src="%s"></script>`,
12271
+ ],
12272
+ module: [
12273
+ `<script type="module" src="%s"></script>`,
12274
+ ],
12275
+ style: [
12276
+ `<link rel="stylesheet" href="%s">`,
12277
+ ],
12278
+ };/**
12083
12279
  * Alias of `os.homedir()`.
12084
12280
  * @type {string}
12085
12281
  */
12086
12282
  const home = os.homedir();
12087
12283
 
12088
12284
  /**
12089
- * Searchs the given file path in the given directories.
12285
+ * Returns or overwrites the extension of the given file path.
12286
+ * @param {string} file - File path
12287
+ * @param {string} [set] - New extension
12288
+ * @return {string} the extension, or a modified file path with the new extension
12289
+ */
12290
+ function ext(file, set = null) {
12291
+ let dot = file.lastIndexOf('.');
12292
+ return typeof set == 'string'
12293
+ ? (dot < 0 ? (file + set) : (file.substring(0, dot) + set))
12294
+ : (dot < 0 ? '' : file.substring(dot));
12295
+ }
12296
+
12297
+ /**
12298
+ * Searches the given file path in the given directories.
12090
12299
  * @param {string} file - File to find
12091
12300
  * @param {string[]} dirs - Array of directories to search
12092
12301
  * @param {object} [opts] - Options
12302
+ * @param {boolean} [opts.allowAbsolute=true] - If true, `file` can be an absolute path
12093
12303
  * @return {string|boolean} found file path, or false if not found
12094
12304
  */
12095
12305
  function find(file, dirs = [], opts = {}) {
@@ -12116,28 +12326,43 @@ function requireBundle$1 () {
12116
12326
  }
12117
12327
 
12118
12328
  /**
12119
- * Deletes the contents of the given directory.
12120
- * @return {Promise}
12121
- */
12122
- function clean(dir, pattern, depth = 1) {
12123
- return exec(`find '${dir}' -type f -name '${pattern}' -maxdepth ${depth} -delete`);
12124
- }
12125
-
12126
- /**
12127
- * Deletes the given file or directory.
12128
- * @param {string} file
12129
- * @return {Promise}
12130
- */
12131
- function rm(file) {
12132
- return fsp__namespace.rm(file, {recursive: true, force: true});
12133
- }
12134
-
12135
- /**
12136
- * Deletes the given file or directory synchronously.
12137
- * @param {string} file
12329
+ * Deletes the files in the given directory.
12330
+ * @param {string} dir - Directory to clean
12331
+ * @param {string|RegExp} [pattern] - File pattern
12332
+ * @param {object} [opts] - Options
12333
+ * @param {boolean} [opts.recursive=false] - Searches recursively
12334
+ * @param {object} [opts.types] - File types to delete
12335
+ * @param {boolean} [opts.types.any=false] - Any type
12336
+ * @param {boolean} [opts.types.file=true] - Regular file
12337
+ * @param {boolean} [opts.types.dir=false] - Directory
12338
+ * @param {boolean} [opts.types.symlink=false] - Symbolic link
12339
+ * @return {Promise} a promise resolved with the deleted file paths
12138
12340
  */
12139
- function rmSync(file) {
12140
- return fs.rmSync(file, {recursive: true, force: true});
12341
+ function clean(dir, pattern = null, opts = {}) {
12342
+ if (pattern && typeof pattern == 'string') pattern = new RegExp(pattern);
12343
+ let {
12344
+ recursive = false,
12345
+ types = {file: true},
12346
+ } = opts;
12347
+ return fsp__namespace.readdir(dir, {recursive, withFileTypes: true}).then(files => {
12348
+ let tasks = [];
12349
+ for (let i = 0; i < files.length; i++) {
12350
+ let f = files[i];
12351
+ if (!types.any) {
12352
+ if (f.isFile()) {
12353
+ if (!types.file) continue;
12354
+ } else if (f.isDirectory()) {
12355
+ if (!types.dir) continue;
12356
+ } else if (f.isSymbolicLink()) {
12357
+ if (!types.symlink) continue;
12358
+ }
12359
+ }
12360
+ f = path.join(dir, f.name);
12361
+ if (pattern && !f.match(pattern)) continue;
12362
+ tasks.push(fsp__namespace.rm(f, {force: true, recursive: true}).then(() => f));
12363
+ }
12364
+ return tasks.length ? Promise.all(tasks) : false;
12365
+ });
12141
12366
  }
12142
12367
 
12143
12368
  /**
@@ -12171,7 +12396,7 @@ function requireBundle$1 () {
12171
12396
  *
12172
12397
  * @example
12173
12398
  * return gulp.src(src)
12174
- * .pipe(modify((data, enc) => {
12399
+ * .pipe(modifyStream((data, enc) => {
12175
12400
  * // do stuff
12176
12401
  * return newData;
12177
12402
  * }));
@@ -12197,25 +12422,31 @@ function requireBundle$1 () {
12197
12422
  }
12198
12423
  }
12199
12424
  });
12200
- }
12201
-
12202
- var io = /*#__PURE__*/Object.freeze({
12203
- __proto__: null,
12204
- clean: clean,
12205
- copy: copy,
12206
- find: find,
12207
- home: home,
12208
- modifyStream: modifyStream,
12209
- rm: rm,
12210
- rmSync: rmSync,
12211
- untilde: untilde
12212
- });
12213
-
12214
- const merge = Object.assign;
12425
+ }var io=/*#__PURE__*/Object.freeze({__proto__:null,AssetImporter:AssetImporter,clean:clean,copy:copy,ext:ext,find:find,home:home,modifyStream:modifyStream,untilde:untilde});const merge = Object.assign;
12215
12426
 
12216
12427
  /*!
12217
- * Test Utils
12218
- * @author amekusa
12428
+ * === @amekusa/util.js/test === *
12429
+ * MIT License
12430
+ *
12431
+ * Copyright (c) 2024 Satoshi Soma
12432
+ *
12433
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
12434
+ * of this software and associated documentation files (the "Software"), to deal
12435
+ * in the Software without restriction, including without limitation the rights
12436
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12437
+ * copies of the Software, and to permit persons to whom the Software is
12438
+ * furnished to do so, subject to the following conditions:
12439
+ *
12440
+ * The above copyright notice and this permission notice shall be included in all
12441
+ * copies or substantial portions of the Software.
12442
+ *
12443
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
12444
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
12445
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
12446
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
12447
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
12448
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
12449
+ * SOFTWARE.
12219
12450
  */
12220
12451
 
12221
12452
  /**
@@ -12282,28 +12513,36 @@ function requireBundle$1 () {
12282
12513
  let testCase = (c, title) => {
12283
12514
  it(title, () => {
12284
12515
  if (typeof c != 'object') invalid(`a test case must be an object`);
12516
+
12285
12517
  // ---- call function ----
12286
- let r;
12287
12518
  let args = [];
12288
- if (c.args) { // args to pass
12519
+ if ('args' in c) { // args to pass
12289
12520
  if (!Array.isArray(c.args)) invalid(`'args' must be an array`);
12290
12521
  args = c.args;
12522
+ delete c.args;
12291
12523
  }
12292
- r = fn(...args);
12293
- // ---- check ----
12294
- if ('returnType' in c) { // check return type
12295
- assertType(r, c.returnType, `return type failed`);
12296
- }
12297
- if ('return' in c) { // check return
12298
- assertEqual(r, c.return, merge({msg: `return value failed`}, opts));
12299
- }
12300
- if (c.test) { // custom test
12301
- if (typeof c.test != 'function') invalid(`'test' must be a function`);
12302
- c.test(r, ...args);
12524
+ let r = fn(...args);
12525
+
12526
+ // ---- check the result ----
12527
+ let check = {
12528
+ returnType() {
12529
+ assertType(r, c.returnType, `return type failed`);
12530
+ },
12531
+ return() {
12532
+ assertEqual(r, c.return, merge({msg: `return value failed`}, opts));
12533
+ },
12534
+ test() {
12535
+ if (typeof c.test != 'function') invalid(`'test' must be a function`);
12536
+ c.test(r, ...args);
12537
+ }
12538
+ };
12539
+ for (let k in c) {
12540
+ if (check[k]) check[k]();
12541
+ else invalid(`invalid property: '${k}' (available properties: ${Object.keys(check).join(', ')})`);
12303
12542
  }
12304
12543
  });
12305
12544
  };
12306
- describe(fn.displayName || fn.name, () => {
12545
+ describe('function: ' + (fn.displayName || fn.name), () => {
12307
12546
  if (Array.isArray(cases)) {
12308
12547
  for (let i = 0; i < cases.length; i++) {
12309
12548
  let c = cases[i];
@@ -12330,52 +12569,68 @@ function requireBundle$1 () {
12330
12569
  let testCase = (c, title) => {
12331
12570
  it(title, () => {
12332
12571
  if (typeof c != 'object') invalid(`a test case must be an object`);
12572
+
12333
12573
  // ---- instantiate ----
12334
12574
  let obj;
12335
12575
  if (opts.static) {
12336
- if (c.initArgs) invalid(`'initArgs' is not for static method`);
12576
+ if ('initArgs' in c) invalid(`'initArgs' is not available for a static method`);
12577
+ if ('prepare' in c) invalid(`'prepare' is not available for a static method`);
12337
12578
  obj = construct;
12338
12579
  } else {
12339
12580
  let initArgs = [];
12340
- if (c.initArgs) {
12581
+ if ('initArgs' in c) {
12341
12582
  if (!Array.isArray(c.initArgs)) invalid(`'initArgs' must be an array`);
12342
12583
  initArgs = c.initArgs;
12584
+ delete c.initArgs;
12343
12585
  }
12344
12586
  try {
12345
12587
  obj = new construct(...initArgs);
12346
12588
  } catch (e) {
12347
12589
  obj = construct(...initArgs);
12348
12590
  }
12591
+ if ('prepare' in c) {
12592
+ if (typeof c.prepare != 'function') invalid(`'prepare' must be a function`);
12593
+ c.prepare(obj);
12594
+ delete c.prepare;
12595
+ }
12349
12596
  }
12597
+
12350
12598
  // ---- call method ----
12351
- if (!(method in obj)) invalid(`no such method as '${method}`);
12352
- let r;
12599
+ if (!(method in obj)) invalid(`no such method as '${method}'`);
12353
12600
  let args = [];
12354
- if (c.args) { // args to pass
12601
+ if ('args' in c) { // args to pass
12355
12602
  if (!Array.isArray(c.args)) invalid(`'args' must be an array`);
12356
12603
  args = c.args;
12604
+ delete c.args;
12357
12605
  }
12358
- r = obj[method](...args);
12359
- // ---- check ----
12360
- if (c.returnsSelf) { // check if returns itself
12361
- assert.strictEqual(r, obj);
12362
- }
12363
- if ('returnType' in c) { // check return type
12364
- assertType(r, c.returnType, `return type failed`);
12365
- }
12366
- if ('return' in c) { // check return value
12367
- assertEqual(r, c.return, merge({msg: `return failed`}, opts));
12368
- }
12369
- if (c.props) { // check properties
12370
- assertProps(obj, c.props, opts);
12371
- }
12372
- if (c.test) { // custom test
12373
- if (typeof c.test != 'function') invalid(`'test' must be a function`);
12374
- c.test(r, obj, ...args);
12606
+ let r = obj[method](...args);
12607
+
12608
+ // ---- check the result ----
12609
+ let check = {
12610
+ returnsSelf() { // check if returns itself
12611
+ assert.strictEqual(r, obj, `must return self`);
12612
+ },
12613
+ returnType() { // check return type
12614
+ assertType(r, c.returnType, `return type failed`);
12615
+ },
12616
+ return() { // check return value
12617
+ assertEqual(r, c.return, merge({msg: `return failed`}, opts));
12618
+ },
12619
+ props() { // check properties
12620
+ assertProps(obj, c.props, opts);
12621
+ },
12622
+ test() { // custom test
12623
+ if (typeof c.test != 'function') invalid(`'test' must be a function`);
12624
+ c.test(r, obj, ...args);
12625
+ }
12626
+ };
12627
+ for (let k in c) {
12628
+ if (check[k]) check[k]();
12629
+ else invalid(`invalid property: '${k}' (available properties: ${Object.keys(check).join(', ')})`);
12375
12630
  }
12376
12631
  });
12377
12632
  };
12378
- describe(construct.name + ' :: ' + method, () => {
12633
+ describe('method: ' + method, () => {
12379
12634
  if (Array.isArray(cases)) {
12380
12635
  for (let i = 0; i < cases.length; i++) {
12381
12636
  let c = cases[i];
@@ -12400,23 +12655,35 @@ function requireBundle$1 () {
12400
12655
  function testInstance(construct, cases, opts = {}) {
12401
12656
  let testCase = (c, title) => {
12402
12657
  it(title, () => {
12403
- let obj;
12658
+ if (typeof c != 'object') invalid(`a test case must be an object`);
12659
+
12660
+ // ---- instantiate ----
12404
12661
  let args = [];
12405
- if (c.args) {
12662
+ if ('args' in c) {
12406
12663
  if (!Array.isArray(c.args)) invalid(`'args' must be an array`);
12407
12664
  args = c.args;
12665
+ delete c.args;
12408
12666
  }
12667
+ let obj;
12409
12668
  try {
12410
12669
  obj = new construct(...args);
12411
12670
  } catch (e) {
12412
12671
  obj = construct(...args);
12413
12672
  }
12414
- if (c.props) { // check properties
12415
- assertProps(obj, c.props, opts);
12416
- }
12417
- if (c.test) { // custom test
12418
- if (typeof c.test != 'function') invalid(`'test' must be a function`);
12419
- c.test(obj, ...args);
12673
+
12674
+ // ---- check the result ----
12675
+ let check = {
12676
+ props() { // check properties
12677
+ assertProps(obj, c.props, opts);
12678
+ },
12679
+ test() { // custom check
12680
+ if (typeof c.test != 'function') invalid(`'test' must be a function`);
12681
+ c.test(obj, ...args);
12682
+ }
12683
+ };
12684
+ for (let k in c) {
12685
+ if (check[k]) check[k]();
12686
+ else invalid(`invalid property: '${k}' (available properties: ${Object.keys(check).join(', ')})`);
12420
12687
  }
12421
12688
  });
12422
12689
  };
@@ -12435,23 +12702,8 @@ function requireBundle$1 () {
12435
12702
  }
12436
12703
  }
12437
12704
  });
12438
- }
12439
-
12440
- var test = /*#__PURE__*/Object.freeze({
12441
- __proto__: null,
12442
- InvalidTest: InvalidTest,
12443
- assertEqual: assertEqual,
12444
- assertProps: assertProps,
12445
- assertType: assertType,
12446
- testFn: testFn,
12447
- testInstance: testInstance,
12448
- testMethod: testMethod
12449
- });
12450
-
12451
- bundle$1.io = io;
12452
- bundle$1.sh = sh;
12453
- bundle$1.test = test;
12454
- return bundle$1;
12705
+ }var test=/*#__PURE__*/Object.freeze({__proto__:null,InvalidTest:InvalidTest,assertEqual:assertEqual,assertProps:assertProps,assertType:assertType,testFn:testFn,testInstance:testInstance,testMethod:testMethod});amekusa_util.arr=arr;amekusa_util.clean=clean$1;amekusa_util.dig=dig;amekusa_util.gen=gen;amekusa_util.io=io;amekusa_util.is=is;amekusa_util.isEmpty=isEmpty;amekusa_util.isEmptyOrFalsey=isEmptyOrFalsey;amekusa_util.isEmptyOrFalsy=isEmptyOrFalsy;amekusa_util.merge=merge$1;amekusa_util.sh=sh;amekusa_util.subst=subst;amekusa_util.test=test;amekusa_util.time=time;amekusa_util.web=web;
12706
+ return amekusa_util;
12455
12707
  }var bundle = {};var hasRequiredBundle;
12456
12708
 
12457
12709
  function requireBundle () {
@@ -12459,12 +12711,12 @@ function requireBundle () {
12459
12711
  hasRequiredBundle = 1;
12460
12712
 
12461
12713
  var node_process = require$$0$1;
12462
- var path = require$$2;
12714
+ var path = require$$3$1;
12463
12715
  var node_child_process = require$$1;
12464
- var os = require$$2$1;
12716
+ var os = require$$0$2;
12465
12717
  var fs = require$$3;
12466
- var fsp = require$$4;
12467
- var node_stream = require$$6$1;
12718
+ var fsp = require$$2;
12719
+ var node_stream = require$$4;
12468
12720
 
12469
12721
 
12470
12722
  function _interopNamespaceDefault(e) {
@@ -13937,9 +14189,9 @@ function requireBundle () {
13937
14189
  bundle.unless_var = unless_var;
13938
14190
  return bundle;
13939
14191
  }var name = "keycomfort";
13940
- var version = "0.2.0";
14192
+ var version = "0.3.0";
13941
14193
  var description = "Comfortable keyboard remaps for Karabiner/AutoHotKey";
13942
- var require$$10 = {
14194
+ var require$$9 = {
13943
14195
  name: name,
13944
14196
  version: version,
13945
14197
  description: description};var rules_1;
@@ -13971,7 +14223,8 @@ function requireRules () {
13971
14223
  },
13972
14224
 
13973
14225
  'cancel modifier'(c, r) {
13974
- r.remap({
14226
+ r.cond(if_var('keycomfort_layer_disable', 0))
14227
+ .remap({
13975
14228
  from: key(c.key, any),
13976
14229
  to: [
13977
14230
  set_var('keycomfort_layer_disable', 1),
@@ -13981,6 +14234,23 @@ function requireRules () {
13981
14234
  });
13982
14235
  },
13983
14236
 
14237
+ 'disable modifier'(c, r) {
14238
+ r.cond(modding)
14239
+ .cond(if_var('keycomfort_layer_disable', 0))
14240
+ .remap({
14241
+ from: key(c.key),
14242
+ to: set_var('keycomfort_layer_disable', 1)
14243
+ });
14244
+ },
14245
+
14246
+ 'enable modifier'(c, r) {
14247
+ r.cond(if_var('keycomfort_layer_disable', 1))
14248
+ .remap({
14249
+ from: key(c.key),
14250
+ to: set_var('keycomfort_layer_disable', 0)
14251
+ });
14252
+ },
14253
+
13984
14254
  'arrows'(c, r) {
13985
14255
  r.cond(modding)
13986
14256
  .remap({
@@ -14118,6 +14388,14 @@ function requireRules () {
14118
14388
  });
14119
14389
  },
14120
14390
 
14391
+ 'delete word'(c, r) {
14392
+ r.cond(modding)
14393
+ .remap({
14394
+ from: key(c.key),
14395
+ to: key('delete_or_backspace', 'option')
14396
+ });
14397
+ },
14398
+
14121
14399
  'edit'(c, r) {
14122
14400
  r.cond(modding)
14123
14401
  .remap({
@@ -14413,6 +14691,14 @@ function requireRules () {
14413
14691
  });
14414
14692
  },
14415
14693
 
14694
+ 'underscore'(c, r) {
14695
+ r.cond(modding)
14696
+ .remap({
14697
+ from: key(c.from),
14698
+ to: key(c.to)
14699
+ });
14700
+ },
14701
+
14416
14702
  'custom'(c, r) {
14417
14703
  if (!c.rules.length) return;
14418
14704
  r.cond(modding);
@@ -14494,13 +14780,12 @@ function requireMain () {
14494
14780
  const {env, cwd, stdin, stdout} = require$$0$1;
14495
14781
  const {spawnSync: spawn} = require$$1;
14496
14782
  const fs = require$$3;
14497
- const path = require$$2;
14783
+ const path = require$$3$1;
14498
14784
  const readline = require$$4$1;
14499
14785
 
14500
14786
  const {Command, Argument} = requireCommander();
14501
14787
  const yaml = require$$6;
14502
- const {merge, isEmpty} = requireBundle$2();
14503
- const {io} = requireBundle$1();
14788
+ const {io, merge, isEmpty} = requireAmekusa_util();
14504
14789
  const {
14505
14790
  RuleSet, Config,
14506
14791
  if_app, unless_app,
@@ -14535,9 +14820,9 @@ function requireMain () {
14535
14820
  *
14536
14821
  */
14537
14822
 
14538
- const pkg = require$$10;
14823
+ const pkg = require$$9;
14539
14824
  const rules = requireRules();
14540
- const defaultsYML = "# === KEYCOMFORT CONFIG ===\n# NOTE:\n# 0 means \"No\"\n# 1 means \"Yes\"\n\npaths:\n karabiner:\n save_as: ~/.config/karabiner/assets/complex_modifications/keycomfort.json\n apply_to: ~/.config/karabiner/karabiner.json\n ahk:\n save_as: ~/Desktop/keycomfort.ahk\n apply_to:\n\nvim_like: 0 # prefer vim-like mappings?\n\nrules: # mapping rules\n\n modifier:\n desc: Use [key] as a special modifier key (Required)\n enable: 1\n key: spacebar\n alone: spacebar\n\n cancel modifier:\n desc: Cancel modifier (<modifier>) with [key]\n enable: 1\n key: left_shift\n\n arrows:\n desc: <modifier> + [up]/[right]/[down]/[left] = Up/Right/Down/Left\n enable: 1\n up: e\n right: f\n down: d\n left: s\n\n page up/down:\n desc: <modifier> + [up]/[down] = Page Up/Down\n enable: 1\n up: w\n down: r\n\n prev/next word:\n desc: <modifier> + [prev]/[next] = Prev/Next Word\n enable: 1\n prev: a\n next: g\n apps:\n sonicpi: 1\n others: 1\n\n line start/end:\n desc: <modifier> + [start]/[end] = Line Start/End\n enable: 1\n start: q\n end: t\n apps:\n terminal: 1\n sonicpi: 1\n others: 1\n\n select:\n desc: <modifier> + [up]/[right]/[down]/[left] = Select Up/Right/Down/Left\n enable: 1\n up: i\n right: l\n down: k\n left: j\n\n vim:\n left: h\n down: j\n up: k\n right: l\n\n indent/outdent:\n desc: <modifier> + [indent]/[outdent] = Indent/Outdent\n enable: 1\n indent: o\n outdent: u\n\n backspace/delete:\n desc: <modifier> + [backspace]/[delete] = Backspace/Delete\n enable: 1\n backspace: n\n delete: m\n\n edit:\n desc: <modifier> + [undo]/[cut]/[copy]/[paste] = Undo/Cut/Copy/Paste\n enable: 1\n undo: z\n cut: x\n copy: c\n paste: v\n\n delete line:\n desc: <modifier> + [key] = Delete Line\n enable: 1\n key: shift + m\n apps:\n atom: 1\n vscode: 1\n eclipse: 1\n\n insert line:\n desc: <modifier> + [key] = New Line Below\n enable: 1\n key: return_or_enter\n apps:\n atom: 1\n vscode: 1\n eclipse: 1\n\n move line:\n desc: <modifier> + [up]/[down] = Move Line Up/Down\n enable: 1\n up: comma\n down: period\n apps:\n atom: 1\n vscode: 1\n eclipse: 1\n sonicpi: 1\n\n left/right tab:\n desc: <modifier> + [left]/[right] = Left/Right Tab\n enable: 1\n left: 2\n right: 3\n apps:\n vscode: 1\n eclipse: 1\n others: 1\n\n close/open tab:\n desc: <modifier> + [close]/[open] = Close/Open Tab\n enable: 1\n close: 1\n open: 4\n\n numpad:\n desc: <modifier> + [trigger] = Numpad Mode ([num1]=1, [num5]=5, [num9]=9)\n enable: 1\n trigger: left_control\n\n num0: b\n num1: n\n num2: m\n num3: comma\n\n num4: j\n num5: k\n num6: l\n\n num7: u\n num8: i\n num9: o\n\n slash: 8\n asterisk: 9\n hyphen: 0\n plus: p\n\n enter: slash\n delete: semicolon\n backspace: h\n\n plus/minus:\n desc: <modifier> + [plus]/[minus] = Plus/Minus\n enable: 1\n plus: p\n minus: shift + p\n to:\n plus: shift + equal_sign\n minus: hyphen\n\n backslash:\n desc: <modifier> + [from] = Backslash\n enable: 1\n from: slash\n to: backslash\n\n backtick:\n desc: <modifier> + [from] = Backtick\n enable: 1\n from: quote\n to: grave_accent_and_tilde\n\n tilde:\n desc: <modifier> + [from] = Tilde\n enable: 1\n from: hyphen\n to: shift + grave_accent_and_tilde\n\n pipe:\n desc: <modifier> + [from] = Pipe\n enable: 1\n from: 7\n to: shift + backslash\n\n equal:\n desc: <modifier> + [from] = Equal Sign\n enable: 1\n from: semicolon\n to: equal_sign\n\n enter:\n desc: <modifier> + [from] = Enter\n enable: 1\n from: tab\n to: return_or_enter\n\n custom:\n desc: <modifier> + Custom Keys\n enable: 1\n rules:\n # Examples\n # - from: p\n # to: shift + equal_sign\n\n remap capslock:\n desc: Caps Lock = [to]/[alone]\n enable: 1\n to: left_control\n alone: escape\n\n remap l-control:\n desc: Left Control = [to]/[alone]\n enable: 1\n to: left_control\n alone: escape\n\n remap r-control:\n desc: Right Control = [to]/[alone]\n enable: 0\n to: right_control\n alone: escape\n\n remap l-command:\n desc: Left Command = [to]/[alone]\n enable: 0\n to: left_command\n alone: left_command\n\n remap r-command:\n desc: Right Command = [to]/[alone]\n enable: 0\n to: right_command\n alone: right_command\n\n remap l-shift:\n desc: Left Shift = [to]/[alone]\n enable: 0\n to: left_shift\n alone: left_shift\n\n remap r-shift:\n desc: Right Shift = [to]/[alone]\n enable: 0\n to: right_shift\n alone: right_shift\n\n\napps:\n others:\n enable: 1\n\n login:\n enable: 1\n id:\n - com.apple.loginwindow\n\n terminal:\n enable: 1\n id:\n - com.apple.Terminal\n - com.googlecode.iterm2\n - org.alacritty\n exe:\n - cmd.exe\n\n vscode:\n enable: 0\n id:\n - com.microsoft.VSCode\n - com.vscodium\n exe:\n - Code.exe\n\n atom:\n enable: 0\n id:\n - com.github.atom\n - dev.pulsar-edit.pulsar\n\n eclipse:\n enable: 0\n id:\n - org.eclipse.platform.ide\n exe:\n - eclipse.exe\n\n sonicpi:\n enable: 0\n id:\n - net.sonic-pi.app\n\n\nkey_labels: # display names for key codes\n spacebar: Space\n return_or_enter: Enter\n grave_accent_and_tilde: Backtick\n japanese_eisuu: 英数\n japanese_kana: かな\n\n";
14825
+ const defaultsYML = "# === KEYCOMFORT CONFIG ===\n# NOTE:\n# 0 means \"No\"\n# 1 means \"Yes\"\n\npaths:\n karabiner:\n save_as: ~/.config/karabiner/assets/complex_modifications/keycomfort.json\n apply_to: ~/.config/karabiner/karabiner.json\n ahk:\n save_as: ~/Desktop/keycomfort.ahk\n apply_to:\n\nvim_like: 0 # prefer vim-like mappings?\n\nrules: # mapping rules\n\n modifier:\n desc: Use [key] as a special modifier key (Required)\n enable: 1\n key: spacebar\n alone: spacebar\n\n cancel modifier:\n desc: Cancel modifier (<modifier>) with [key]\n enable: 1\n key: left_shift\n\n disable modifier:\n desc: Disable modifier (<modifier>) with <modifier> + [key]\n enable: 1\n key: right_shift + escape\n\n enable modifier:\n desc: Enable modifier (<modifier>) with [key]\n enable: 1\n key: right_shift + escape\n\n arrows:\n desc: <modifier> + { [up] / [right] / [down] / [left] } = Up / Right / Down / Left\n enable: 1\n up: e\n right: f\n down: d\n left: s\n\n page up/down:\n desc: <modifier> + { [up] / [down] } = Page Up / Down\n enable: 1\n up: w\n down: r\n\n prev/next word:\n desc: <modifier> + { [prev] / [next] } = Prev / Next Word\n enable: 1\n prev: a\n next: g\n apps:\n sonicpi: 1\n others: 1\n\n line start/end:\n desc: <modifier> + { [start] / [end] } = Line Start / End\n enable: 1\n start: q\n end: t\n apps:\n terminal: 1\n sonicpi: 1\n others: 1\n\n select:\n desc: <modifier> + { [up] / [right] / [down] / [left] } = Select Up / Right / Down / Left\n enable: 1\n up: i\n right: l\n down: k\n left: j\n vim:\n left: h\n down: j\n up: k\n right: l\n\n indent/outdent:\n desc: <modifier> + { [indent] / [outdent] } = Indent / Outdent\n enable: 1\n indent: o\n outdent: u\n\n backspace/delete:\n desc: <modifier> + { [backspace] / [delete] } = Backspace / Delete\n enable: 1\n backspace: n\n delete: m\n\n delete word:\n desc: <modifier> + [key] = Delete Word\n enable: 1\n key: b\n\n edit:\n desc: <modifier> + { [undo] / [cut] / [copy] / [paste] } = Undo / Cut / Copy / Paste\n enable: 1\n undo: z\n cut: x\n copy: c\n paste: v\n\n delete line:\n desc: <modifier> + [key] = Delete Line\n enable: 1\n key: shift + m\n apps:\n atom: 1\n vscode: 1\n eclipse: 1\n\n insert line:\n desc: <modifier> + [key] = New Line Below\n enable: 1\n key: return_or_enter\n apps:\n atom: 1\n vscode: 1\n eclipse: 1\n\n move line:\n desc: <modifier> + { [up] / [down] } = Move Line Up / Down\n enable: 1\n up: shift + i\n down: shift + k\n vim:\n up: shift + k\n down: shift + j\n apps:\n atom: 1\n vscode: 1\n eclipse: 1\n sonicpi: 1\n\n left/right tab:\n desc: <modifier> + { [left] / [right] } = Left / Right Tab\n enable: 1\n left: 2\n right: 3\n apps:\n vscode: 1\n eclipse: 1\n others: 1\n\n close/open tab:\n desc: <modifier> + { [close] / [open] } = Close / Open Tab\n enable: 1\n close: 1\n open: 4\n\n numpad:\n desc: <modifier> + [trigger] = Numpad Mode ([num1]=1, [num5]=5, [num9]=9)\n enable: 1\n trigger: left_control\n\n num0: b\n num1: n\n num2: m\n num3: comma\n\n num4: j\n num5: k\n num6: l\n\n num7: u\n num8: i\n num9: o\n\n slash: 8\n asterisk: 9\n hyphen: 0\n plus: p\n\n enter: slash\n delete: semicolon\n backspace: h\n\n plus/minus:\n desc: <modifier> + { [plus] / [minus] } = Plus / Minus\n enable: 1\n plus: p\n minus: shift + p\n to:\n plus: shift + equal_sign\n minus: hyphen\n\n backslash:\n desc: <modifier> + [from] = Backslash\n enable: 1\n from: slash\n to: backslash\n\n backtick:\n desc: <modifier> + [from] = Backtick\n enable: 1\n from: quote\n to: grave_accent_and_tilde\n\n tilde:\n desc: <modifier> + [from] = Tilde\n enable: 1\n from: hyphen\n to: shift + grave_accent_and_tilde\n\n pipe:\n desc: <modifier> + [from] = Pipe\n enable: 1\n from: 7\n to: shift + backslash\n\n equal:\n desc: <modifier> + [from] = Equal Sign\n enable: 1\n from: semicolon\n to: equal_sign\n\n enter:\n desc: <modifier> + [from] = Enter\n enable: 1\n from: tab\n to: return_or_enter\n\n underscore:\n desc: <modifier> + [from] = Underscore\n enable: 1\n from: period\n to: shift + hyphen\n\n custom:\n desc: <modifier> + Custom Keys\n enable: 1\n rules:\n # Examples\n # - from: p\n # to: shift + equal_sign\n\n remap capslock:\n desc: Caps Lock = [to] / [alone]\n enable: 1\n to: left_control\n alone: escape\n\n remap l-control:\n desc: Left Control = [to] / [alone]\n enable: 1\n to: left_control\n alone: escape\n\n remap r-control:\n desc: Right Control = [to] / [alone]\n enable: 0\n to: right_control\n alone: escape\n\n remap l-command:\n desc: Left Command = [to] / [alone]\n enable: 0\n to: left_command\n alone: left_command\n\n remap r-command:\n desc: Right Command = [to] / [alone]\n enable: 0\n to: right_command\n alone: right_command\n\n remap l-shift:\n desc: Left Shift = [to] / [alone]\n enable: 0\n to: left_shift\n alone: left_shift\n\n remap r-shift:\n desc: Right Shift = [to] / [alone]\n enable: 0\n to: right_shift\n alone: right_shift\n\n\napps:\n others:\n enable: 1\n\n login:\n enable: 1\n id:\n - com.apple.loginwindow\n\n terminal:\n enable: 1\n id:\n - com.apple.Terminal\n - com.googlecode.iterm2\n - org.alacritty\n exe:\n - cmd.exe\n\n vscode:\n enable: 0\n id:\n - com.microsoft.VSCode\n - com.vscodium\n exe:\n - Code.exe\n\n atom:\n enable: 0\n id:\n - com.github.atom\n - dev.pulsar-edit.pulsar\n\n eclipse:\n enable: 0\n id:\n - org.eclipse.platform.ide\n exe:\n - eclipse.exe\n\n sonicpi:\n enable: 0\n id:\n - net.sonic-pi.app\n\n\nkey_labels: # display names for key codes\n spacebar: Space\n return_or_enter: Enter\n grave_accent_and_tilde: Backtick\n japanese_eisuu: 英数\n japanese_kana: かな\n\n";
14541
14826
  const defaults = yaml.parse(defaultsYML);
14542
14827
  const defaultConfig = loc(io.home, '.config', 'keycomfort', 'config.yml');
14543
14828
 
@@ -14579,10 +14864,18 @@ function requireMain () {
14579
14864
  }
14580
14865
 
14581
14866
  function label(key, dict) {
14582
- if (Array.isArray(key)) return key.map(I => label(I, dict)).join(',');
14583
- key += '';
14867
+ if (Array.isArray(key)) return key.map(I => label(I.trim(), dict)).join(', ');
14868
+ key = `${key}`.trim();
14869
+ if (key.includes(',')) return label(key.split(','), dict);
14870
+ if (key.includes('+')) return key.split('+').map(I => label(I.trim(), dict)).join(' + ');
14584
14871
  if (key in dict) return dict[key];
14585
- return key.split('_').map(I => I.charAt(0).toUpperCase() + I.slice(1)).join(' ');
14872
+ let lr = ''; // left or right
14873
+ let m = key.match(/^(left|right)_([_a-z0-9]+)$/i);
14874
+ if (m) {
14875
+ lr = m[1] == 'left' ? 'L-' : 'R-';
14876
+ key = m[2];
14877
+ }
14878
+ return lr + key.split('_').map(I => I.charAt(0).toUpperCase() + I.slice(1)).join(' ');
14586
14879
  }
14587
14880
 
14588
14881
  const app = new Command();
@@ -14727,8 +15020,9 @@ function requireMain () {
14727
15020
  if (rc.vim && vim) rc = merge(rc, rc.vim);
14728
15021
 
14729
15022
  // format rule description
14730
- let desc = rc.desc.replaceAll('<modifier>', label(modifier, labels));
14731
- for (let i in rc) desc = desc.replaceAll(`[${i}]`, label(rc[i], labels));
15023
+ let desc = rc.desc.replaceAll(/(?:<modifier>|\[([_0-9a-z]+)\])/gi, (_, m1) => {
15024
+ return label(m1 ? rc[m1] : modifier, labels);
15025
+ });
14732
15026
 
14733
15027
  let rule = rules[i];
14734
15028
  let newRule;