@iebh/reflib 2.7.2 → 2.8.1

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.
@@ -1,44 +1,44 @@
1
- import Emitter from '../../shared/emitter.js';
2
-
3
- export default class BrowserJSONStream {
4
- constructor() {
5
- this.text = '';
6
- this.emitter = Emitter();
7
- this.recNumber = 1;
8
- }
9
-
10
- write(data) {
11
- // CF: TODO: Parse data as it comes in chunks for better memory efficiency
12
- this.text += data;
13
- }
14
-
15
- end() {
16
- try {
17
- // Parse this.text as JSON
18
- const jsonArray = JSON.parse(this.text);
19
-
20
- // Free memory
21
- this.text = '';
22
-
23
- // For each entry in the json array (as ref):
24
- if (Array.isArray(jsonArray))
25
- jsonArray.forEach(ref => {
26
- this.emitter.emit('data', ref);
27
- });
28
-
29
- // Finished
30
- this.emitter.emit('end');
31
- } catch (e) {
32
- console.error('Error parsing final JSON:', e);
33
- this.emitter.emit('error', e);
34
- }
35
- }
36
-
37
- on(event, listener) {
38
- this.emitter.on(event, listener);
39
- }
40
-
41
- static parse() {
42
- return new BrowserJSONStream();
43
- }
1
+ import Emitter from '../../shared/emitter.js';
2
+
3
+ export default class BrowserJSONStream {
4
+ constructor() {
5
+ this.text = '';
6
+ this.emitter = Emitter();
7
+ this.recNumber = 1;
8
+ }
9
+
10
+ write(data) {
11
+ // CF: TODO: Parse data as it comes in chunks for better memory efficiency
12
+ this.text += data;
13
+ }
14
+
15
+ end() {
16
+ try {
17
+ // Parse this.text as JSON
18
+ const jsonArray = JSON.parse(this.text);
19
+
20
+ // Free memory
21
+ this.text = '';
22
+
23
+ // For each entry in the json array (as ref):
24
+ if (Array.isArray(jsonArray))
25
+ jsonArray.forEach(ref => {
26
+ this.emitter.emit('data', ref);
27
+ });
28
+
29
+ // Finished
30
+ this.emitter.emit('end');
31
+ } catch (e) {
32
+ console.error('Error parsing final JSON:', e);
33
+ this.emitter.emit('error', e);
34
+ }
35
+ }
36
+
37
+ on(event, listener) {
38
+ this.emitter.on(event, listener);
39
+ }
40
+
41
+ static parse() {
42
+ return new BrowserJSONStream();
43
+ }
44
44
  }
@@ -1,52 +1,52 @@
1
- import Emitter from '../../shared/emitter.js';
2
- import CacxParser from '@iebh/cacx';
3
-
4
- export class WritableStream {
5
- constructor(passedParserOptions) {
6
- this.emitter = Emitter();
7
- this.parser = new CacxParser({
8
- collect: false,
9
- reAttrSegment: /(?<key>[a-zA-Z_:][-a-zA-Z0-9_:.]*)(?:\s*=\s*(?<escape>["'])(?<val>.*?)\k<escape>)?/ig,
10
- onTagOpen: (node) => {
11
- const name = node.tag;
12
- const attrs = node.attrs || {};
13
- this.emitter.emit('opentag', name, attrs);
14
- },
15
- onTagClose: (node) => {
16
- const name = node.tag;
17
- // Emit text event before closing the tag
18
- if (node.text && node.text.trim()) {
19
- this.emitter.emit('text', node.text.trim());
20
- }
21
- this.emitter.emit('closetag', name);
22
- },
23
- flattenText: false,
24
- keyText: 'text',
25
- keyAttrs: 'attrs',
26
- });
27
-
28
- // Add event listeners to mimic htmlparser2 behavior
29
- this.emitter.on('opentag', passedParserOptions.onopentag);
30
- this.emitter.on('closetag', passedParserOptions.onclosetag);
31
- this.emitter.on('text', passedParserOptions.ontext);
32
- this.emitter.on('end', passedParserOptions.onend);
33
- }
34
-
35
- write(data) {
36
- this.parser.append(data).exec();
37
- }
38
-
39
- end() {
40
- // Process any remaining data
41
- this.parser.exec();
42
-
43
- // Emit text event for the last node if it has text
44
- const lastNode = this.parser.stack.at(-1);
45
- if (lastNode && lastNode.text && lastNode.text.trim()) {
46
- this.emitter.emit('text', lastNode.text.trim());
47
- }
48
-
49
- // Emit end event
50
- this.emitter.emit('end');
51
- }
52
- }
1
+ import Emitter from '../../shared/emitter.js';
2
+ import CacxParser from '@iebh/cacx';
3
+
4
+ export class WritableStream {
5
+ constructor(passedParserOptions) {
6
+ this.emitter = Emitter();
7
+ this.parser = new CacxParser({
8
+ collect: false,
9
+ reAttrSegment: /(?<key>[a-zA-Z_:][-a-zA-Z0-9_:.]*)(?:\s*=\s*(?<escape>["'])(?<val>.*?)\k<escape>)?/ig,
10
+ onTagOpen: (node) => {
11
+ const name = node.tag;
12
+ const attrs = node.attrs || {};
13
+ this.emitter.emit('opentag', name, attrs);
14
+ },
15
+ onTagClose: (node) => {
16
+ const name = node.tag;
17
+ // Emit text event before closing the tag
18
+ if (node.text && node.text.trim()) {
19
+ this.emitter.emit('text', node.text.trim());
20
+ }
21
+ this.emitter.emit('closetag', name);
22
+ },
23
+ flattenText: false,
24
+ keyText: 'text',
25
+ keyAttrs: 'attrs',
26
+ });
27
+
28
+ // Add event listeners
29
+ this.emitter.on('opentag', passedParserOptions.onopentag);
30
+ this.emitter.on('closetag', passedParserOptions.onclosetag);
31
+ this.emitter.on('text', passedParserOptions.ontext);
32
+ this.emitter.on('end', passedParserOptions.onend);
33
+ }
34
+
35
+ write(data) {
36
+ this.parser.append(data).exec();
37
+ }
38
+
39
+ end() {
40
+ // Process any remaining data
41
+ this.parser.exec();
42
+
43
+ // Emit text event for the last node if it has text
44
+ const lastNode = this.parser.stack.at(-1);
45
+ if (lastNode && lastNode.text && lastNode.text.trim()) {
46
+ this.emitter.emit('text', lastNode.text.trim());
47
+ }
48
+
49
+ // Emit end event
50
+ this.emitter.emit('end');
51
+ }
52
+ }
package/package.json CHANGED
@@ -1,66 +1,68 @@
1
- {
2
- "name": "@iebh/reflib",
3
- "version": "2.7.2",
4
- "description": "Reference / Citation reference library utilities",
5
- "scripts": {
6
- "lint": "eslint",
7
- "test": "testa",
8
- "test:browser": "cd test/browser && npm run dev",
9
- "test:watch": "nodemon --exec npm run test"
10
- },
11
- "repository": {
12
- "type": "git",
13
- "url": "git+https://github.com/IEBH/Reflib.git"
14
- },
15
- "keywords": [
16
- "reflib",
17
- "references",
18
- "citations",
19
- "library"
20
- ],
21
- "author": "Matt Carter <m@ttcarter.com> (https://github.com/hash-bang)",
22
- "contributors": [
23
- "Connor Forbes <cforbes.software@gmail.com> (https://github.com/connorf25)",
24
- "Irene Priya Jose (https://github.com/s5333384)",
25
- "Tian Liang (https://github.com/Octian)"
26
- ],
27
- "license": "MIT",
28
- "bugs": {
29
- "url": "https://github.com/IEBH/Reflib/issues"
30
- },
31
- "homepage": "https://github.com/IEBH/Reflib",
32
- "enginesStrict": true,
33
- "engines": {
34
- "node": ">=16.6.0"
35
- },
36
- "type": "module",
37
- "exports": {
38
- ".": {
39
- "browser": "./lib/browser.js",
40
- "default": "./lib/default.js"
41
- },
42
- "./*": "./lib/*.js"
43
- },
44
- "browser": {
45
- "htmlparser2/lib/WritableStream": "./modules/shims/WritableStream-browser.js",
46
- "htmlparser2/lib/esm/WritableStream": "./modules/shims/WritableStream-browser.js",
47
- "JSONStream": "./modules/shims/JSONStream-browser.js"
48
- },
49
- "dependencies": {
50
- "@iebh/cacx": "^1.0.3",
51
- "@zip.js/zip.js": "^2.7.57",
52
- "htmlparser2": "^9.1.0",
53
- "JSONStream": "^1.3.5",
54
- "lodash-es": "^4.17.22",
55
- "mitt": "^3.0.1",
56
- "sql.js": "^1.12.0"
57
- },
58
- "devDependencies": {
59
- "@momsfriendlydevco/eslint-config": "^2.3.1",
60
- "@momsfriendlydevco/testa": "^1.1.2",
61
- "eslint": "^9.31.0",
62
- "nodemon": "^3.1.9",
63
- "temp": "^0.9.4",
64
- "vite-plugin-replace": "^0.1.1"
65
- }
66
- }
1
+ {
2
+ "name": "@iebh/reflib",
3
+ "version": "2.8.1",
4
+ "description": "Reference / Citation reference library utilities",
5
+ "bin": {
6
+ "reflib": "./app.js"
7
+ },
8
+ "scripts": {
9
+ "lint": "eslint",
10
+ "test": "testa",
11
+ "test:browser": "cd test/browser && npm run dev",
12
+ "test:watch": "nodemon --exec npm run test"
13
+ },
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/IEBH/Reflib.git"
17
+ },
18
+ "keywords": [
19
+ "reflib",
20
+ "references",
21
+ "citations",
22
+ "library"
23
+ ],
24
+ "author": "Matt Carter <m@ttcarter.com> (https://github.com/hash-bang)",
25
+ "contributors": [
26
+ "Connor Forbes <cforbes.software@gmail.com> (https://github.com/connorf25)",
27
+ "Irene Priya Jose (https://github.com/s5333384)",
28
+ "Tian Liang (https://github.com/Octian)"
29
+ ],
30
+ "license": "MIT",
31
+ "bugs": {
32
+ "url": "https://github.com/IEBH/Reflib/issues"
33
+ },
34
+ "homepage": "https://github.com/IEBH/Reflib",
35
+ "enginesStrict": true,
36
+ "engines": {
37
+ "node": ">=16.6.0"
38
+ },
39
+ "type": "module",
40
+ "exports": {
41
+ ".": {
42
+ "browser": "./lib/browser.js",
43
+ "default": "./lib/default.js"
44
+ },
45
+ "./*": "./lib/*.js"
46
+ },
47
+ "browser": {
48
+ "JSONStream": "./modules/shims/JSONStream-browser.js"
49
+ },
50
+ "dependencies": {
51
+ "@iebh/cacx": "^1.1.0",
52
+ "@momsfriendlydevco/sort-keys": "^1.0.0",
53
+ "@zip.js/zip.js": "^2.7.57",
54
+ "JSONStream": "^1.3.5",
55
+ "mitt": "^3.0.1",
56
+ "sql.js": "^1.12.0"
57
+ },
58
+ "devDependencies": {
59
+ "@momsfriendlydevco/eslint-config": "^2.3.1",
60
+ "@momsfriendlydevco/testa": "^1.2.0",
61
+ "eslint": "^9.31.0",
62
+ "execa": "^9.6.1",
63
+ "lodash-es": "^4.17.23",
64
+ "nodemon": "^3.1.9",
65
+ "temp": "^0.9.4",
66
+ "vite-plugin-replace": "^0.1.1"
67
+ }
68
+ }
@@ -1,17 +1,17 @@
1
- /**
2
- * Camel case any input string
3
- * This is functionally the same as Lodash's camelCase() function
4
- *
5
- * @param {string} input The input string to camelize
6
- * @returns {string} The input string in camelCase format
7
- * @url https://github.com/MomsFriendlyDevCo/Nodash
8
- */
9
- export default function(input) {
10
- return input
11
- .split(/[\s-]/)
12
- .map((word, offset) => offset == 0
13
- ? word.toLowerCase()
14
- : word.substr(0, 1).toUpperCase() + word.substr(1).toLowerCase()
15
- )
16
- .join('')
17
- }
1
+ /**
2
+ * Camel case any input string
3
+ * This is functionally the same as Lodash's camelCase() function
4
+ *
5
+ * @param {string} input The input string to camelize
6
+ * @returns {string} The input string in camelCase format
7
+ * @url https://github.com/MomsFriendlyDevCo/Nodash
8
+ */
9
+ export default function(input) {
10
+ return input
11
+ .split(/[\s-]/)
12
+ .map((word, offset) => offset == 0
13
+ ? word.toLowerCase()
14
+ : word.substr(0, 1).toUpperCase() + word.substr(1).toLowerCase()
15
+ )
16
+ .join('')
17
+ }
package/shared/emitter.js CHANGED
@@ -1,23 +1,23 @@
1
- import Mitt from 'mitt';
2
-
3
- /**
4
- * Generic wrapper for an event emitter
5
- * This module returns a wrapped version of `mitt` stand-alone event emitter (+ support for method chaining)
6
- *
7
- * @returns {Object} A wrapped version of the NPM Mitt event emitter with chainable functionality
8
- */
9
- export default function emitter() {
10
- let emitter = Mitt();
11
-
12
- // Add method chaining
13
- emitter.mitt = {};
14
- ['on', 'off', 'emit'].forEach(f => {
15
- emitter.mitt[f] = emitter[f]; // Backup old function into `.mitt.${F}`
16
- emitter[f] = (...args) => {
17
- emitter.mitt[f](...args);
18
- return emitter;
19
- };
20
- });
21
-
22
- return emitter;
23
- }
1
+ import Mitt from 'mitt';
2
+
3
+ /**
4
+ * Generic wrapper for an event emitter
5
+ * This module returns a wrapped version of `mitt` stand-alone event emitter (+ support for method chaining)
6
+ *
7
+ * @returns {Object} A wrapped version of the NPM Mitt event emitter with chainable functionality
8
+ */
9
+ export default function emitter() {
10
+ let emitter = Mitt();
11
+
12
+ // Add method chaining
13
+ emitter.mitt = {};
14
+ ['on', 'off', 'emit'].forEach(f => {
15
+ emitter.mitt[f] = emitter[f]; // Backup old function into `.mitt.${F}`
16
+ emitter[f] = (...args) => {
17
+ emitter.mitt[f](...args);
18
+ return emitter;
19
+ };
20
+ });
21
+
22
+ return emitter;
23
+ }
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Attempt to parse an process.argv like array into extracted flags and their values
3
+ *
4
+ * Supported:
5
+ * `--flag` → `true`
6
+ * `--flag val` → `'val'`
7
+ * `--flag a b c` → `['a', 'b', 'c']` (variadic, stops at next flag)
8
+ * `-f` → `true`
9
+ * `-abc` → `{ a: true, b: true, c: true }`
10
+ * `-n 1 2 3` → `{ n: ['1', '2', '3'] }` (last short flag is variadic)
11
+ * `--` → remaining args go to `_`
12
+ * Bare positional args → `_` array
13
+ *
14
+ * @param {Array<String>} [argv=process.argv] `argv` like array to parse, If omitted `process.argv.slice(2)` is used
15
+ * @returns {Object} An object with extracted flags
16
+ *
17
+ * @example Basic argv example
18
+ * parseArgs([
19
+ * '--name', 'Alice', 'Bob', '--verbose', '-n', '42', '-xvf', 'file1', 'file2',
20
+ * ]) //= {
21
+ * _: [],
22
+ * name: ['Alice', 'Bob'],
23
+ * verbose: true,
24
+ * n: '42',
25
+ * x: true,
26
+ * v: true,
27
+ * f: ['file1', 'file2']
28
+ * }
29
+ */
30
+ export function parse(argv = process.argv.slice(2)) {
31
+ let result = { _: [] }
32
+ let i = 0
33
+
34
+ let collectValues = (args, start) => {
35
+ let values = []
36
+ let j = start
37
+ while (j < args.length && !args[j].startsWith('-')) {
38
+ values.push(args[j++])
39
+ }
40
+ return { values, next: j }
41
+ }
42
+
43
+ while (i < argv.length) {
44
+ let arg = argv[i]
45
+
46
+ if (arg.startsWith('--')) {
47
+ let key = arg.slice(2)
48
+ if (!key) { i++; break } // -- separator
49
+ let { values, next } = collectValues(argv, i + 1)
50
+ result[key] = values.length === 0 ? true
51
+ : values.length === 1 ? values[0]
52
+ : values
53
+ i = next
54
+ } else if (arg.startsWith('-')) {
55
+ let flags = arg.slice(1)
56
+ // Check if last char is the one collecting values
57
+ for (let f = 0; f < flags.length - 1; f++) {
58
+ result[flags[f]] = true
59
+ }
60
+ let lastFlag = flags.at(-1);
61
+ let { values, next } = collectValues(argv, i + 1);
62
+ result[lastFlag] = values.length === 0 ? true
63
+ : values.length === 1 ? values[0]
64
+ : values
65
+ i = next;
66
+ } else {
67
+ result._.push(arg)
68
+ i++
69
+ }
70
+ }
71
+
72
+ // Remaining after -- go to _
73
+ while (i < argv.length) result._.push(argv[i++])
74
+
75
+ return result
76
+ }
77
+
78
+
79
+ /**
80
+ * Accept a parsed args object and expand short-flags into long-flags
81
+ *
82
+ * @param {Object} args Parsed arg object to process
83
+ * @param {Object} argMap Object of `shortFlag:String => longFlag:String` translations to apply
84
+ *
85
+ * @returns {Object} The input `args` object with all short-flags translated to long-flags
86
+ */
87
+ export function expand(args, argMap) {
88
+ let outArgs = {...args}; // Shallow copy of incoming args object we are going to mutate
89
+
90
+ Object.entries(argMap)
91
+ .filter(([shortFlag]) => args[shortFlag])
92
+ .map(([shortFlag, longFlag]) => {
93
+ outArgs[longFlag] = outArgs[shortFlag];
94
+ delete outArgs[shortFlag];
95
+ });
96
+
97
+ return outArgs;
98
+ }
99
+
100
+
101
+ export default {
102
+ expand,
103
+ parse,
104
+ }
@@ -1,61 +1,61 @@
1
- import Emitter from '../shared/emitter.js';
2
-
3
- /**
4
- * Wrapper for streams which transforms a given input into an emitter pattern
5
- * This is designed to let regular `node:stream.Readable` objects pass through without alteration but browser based stream objects get wrapped
6
- * @param {stream.Readable|ReadableStream} inStream The input stream to wrap
7
- * @returns {stream.Readable|Emitter} Either the unedited node compatible stream or an event emitter with the same behaviour
8
- *
9
- * @emits data Emitted as `(chunk)` on each data chunk
10
- * @emits end Emitted as `()` when the input stream has closed
11
- * @emits error Emitted as `(Error)` on any read error
12
- */
13
- export default function streamEmitter (inStream) {
14
- if (inStream.getReader) { // Assume browser compatible ReadableStream
15
- /**
16
- * MC's tiny ReadableStream -> stream.Readable / Emitter pattern
17
- * There is a non-zero chance that this is going to break at some future point
18
- * This is really just a shiv for browser functionality to replicate Stream-a-like emitter pattern
19
- * @date 2023-10-19
20
- * @author Matt Carter <m@ttcarter.com>
21
- */
22
- let reader = new Emitter();
23
- Object.assign(reader, {
24
- isBrowser: true, // Tells us we are in browser env
25
- bytesRead: 0,
26
- reader: inStream.getReader(),
27
- textDecoder: new TextDecoder('utf-8'),
28
- read() { // Read one chunk + trigger emitters
29
- this.reader.read()
30
- .then(({done, value}) => {
31
- if (done) {
32
- reader.emit('end');
33
-
34
- if (this.pipeTarget)
35
- this.pipeTarget.end();
36
- } else if (value) {
37
- let data = this.textDecoder.decode(value);
38
- this.bytesRead += data.length;
39
- reader.emit('data', data);
40
-
41
- if (this.pipeTarget)
42
- this.pipeTarget.write(data);
43
-
44
- setTimeout(this.read.bind(this));
45
- }
46
- })
47
- .catch(e => this.emit('error', e))
48
- },
49
- pipeTarget: null,
50
- pipe(target) {
51
- this.pipeTarget = target;
52
- return this;
53
- },
54
- });
55
-
56
- setTimeout(()=> reader.read());
57
- return reader;
58
- } else { // Assume Node native stream.Readable
59
- return inStream;
60
- }
61
- }
1
+ import Emitter from '../shared/emitter.js';
2
+
3
+ /**
4
+ * Wrapper for streams which transforms a given input into an emitter pattern
5
+ * This is designed to let regular `node:stream.Readable` objects pass through without alteration but browser based stream objects get wrapped
6
+ * @param {stream.Readable|ReadableStream} inStream The input stream to wrap
7
+ * @returns {stream.Readable|Emitter} Either the unedited node compatible stream or an event emitter with the same behaviour
8
+ *
9
+ * @emits data Emitted as `(chunk)` on each data chunk
10
+ * @emits end Emitted as `()` when the input stream has closed
11
+ * @emits error Emitted as `(Error)` on any read error
12
+ */
13
+ export default function streamEmitter (inStream) {
14
+ if (inStream.getReader) { // Assume browser compatible ReadableStream
15
+ /**
16
+ * MC's tiny ReadableStream -> stream.Readable / Emitter pattern
17
+ * There is a non-zero chance that this is going to break at some future point
18
+ * This is really just a shiv for browser functionality to replicate Stream-a-like emitter pattern
19
+ * @date 2023-10-19
20
+ * @author Matt Carter <m@ttcarter.com>
21
+ */
22
+ let reader = new Emitter();
23
+ Object.assign(reader, {
24
+ isBrowser: true, // Tells us we are in browser env
25
+ bytesRead: 0,
26
+ reader: inStream.getReader(),
27
+ textDecoder: new TextDecoder('utf-8'),
28
+ read() { // Read one chunk + trigger emitters
29
+ this.reader.read()
30
+ .then(({done, value}) => {
31
+ if (done) {
32
+ reader.emit('end');
33
+
34
+ if (this.pipeTarget)
35
+ this.pipeTarget.end();
36
+ } else if (value) {
37
+ let data = this.textDecoder.decode(value);
38
+ this.bytesRead += data.length;
39
+ reader.emit('data', data);
40
+
41
+ if (this.pipeTarget)
42
+ this.pipeTarget.write(data);
43
+
44
+ setTimeout(this.read.bind(this));
45
+ }
46
+ })
47
+ .catch(e => this.emit('error', e))
48
+ },
49
+ pipeTarget: null,
50
+ pipe(target) {
51
+ this.pipeTarget = target;
52
+ return this;
53
+ },
54
+ });
55
+
56
+ setTimeout(()=> reader.read());
57
+ return reader;
58
+ } else { // Assume Node native stream.Readable
59
+ return inStream;
60
+ }
61
+ }