@iebh/reflib 2.7.0 → 2.8.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.
package/README.md CHANGED
@@ -30,6 +30,29 @@ Compatibility
30
30
  * Medline seems to implement a totally different [publication type system](https://www.nlm.nih.gov/mesh/pubtypes.html) than others. Reflib will attempt to guess the best match, storing the original type in the `medlineType` key. Should the citation library be exported _back_ to Medline / `.nbib` files this key will take precedence to avoid data loss
31
31
 
32
32
 
33
+ Command Line Interface
34
+ ======================
35
+ This NPM ships with a very basic Command Line Interface (CLI) for very basic Reflib files manipulation.
36
+
37
+ ```
38
+ Usage: reflib -i INPUT_FILE [-f FORMAT] [-o -|OUTPUT_FILE]
39
+
40
+
41
+ -i, --input <file> Input file to process
42
+
43
+ -o, --output <file> Output file to save. Use '-' for STDOUT
44
+
45
+ -f, --format <reflib-format> Override or set the file output type (if omitted the outfile filename
46
+ is used to determine the format)
47
+
48
+ -v, --verbose Be verbose when processing
49
+
50
+ --version Print CLI version and exit
51
+
52
+ -h, --help This help screen
53
+ ```
54
+
55
+
33
56
  Reference Structure
34
57
  ===================
35
58
  Reflib creates a simple Plain-Old-JavaScript-Object (POJO) for each reference it parses, or writes to a file format when given a collection of the same.
package/app.js ADDED
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/node
2
+
3
+ import parseArgs from './shared/parseArgs.js';
4
+ import packageInfo from './package.json' with {type: 'json'};
5
+ import reflib from './lib/default.js';
6
+
7
+ let help = `
8
+
9
+ Usage: reflib -i INPUT_FILE [-f FORMAT] [-o -|OUTPUT_FILE]
10
+
11
+
12
+ -i, --input <file> Input file to process
13
+
14
+ -o, --output <file> Output file to save. Use '-' for STDOUT
15
+
16
+ -f, --format <reflib-format> Override or set the file output type (if omitted the outfile filename
17
+ is used to determine the format)
18
+
19
+ -v, --verbose Be verbose when processing
20
+
21
+ --version Print CLI version and exit
22
+
23
+ -h, --help This help screen
24
+ `;
25
+
26
+ // Parse args {{{
27
+ let args = parseArgs.parse();
28
+ args = parseArgs.expand(args, {
29
+ 'h': 'help',
30
+ 'i': 'input',
31
+ 'o': 'output',
32
+ 'f': 'format',
33
+ 'v': 'verbose',
34
+ });
35
+ // }}}
36
+
37
+ // Action: Version {{{
38
+ if (args.version) {
39
+ console.log(`Version: ${packageInfo.version}`);
40
+ process.exit(0);
41
+ }
42
+ // }}}
43
+ // Action: Convert (if --input) {{{
44
+ if (args.input) {
45
+ if (args.verbose) console.log('Reading', args.input);
46
+ let refs = await reflib.readFile(args.input);
47
+ if (args.verbose) console.log('Read', refs.length, 'refs');
48
+
49
+ if (!args.output) {
50
+ if (args.verbose) console.log('No output file specified - assuming STDOUT JSON');
51
+ console.log(JSON.stringify(refs, null, 2));
52
+ } else if (args.output == '-' || args.output === true) {
53
+ if (!args.format) {
54
+ if (args.verbose) console.log('No STDOUT format specified - assuming JSON');
55
+ console.log(JSON.stringify(refs, null, 2));
56
+ } else {
57
+ if (args.verbose) console.log(`Raw output to STDOUT using "${args.format}" format`);
58
+
59
+ let stream = reflib.writeStream(args.format, process.stdout);
60
+ await stream.start();
61
+ await Array.fromAsync(refs, ref =>
62
+ stream.write(ref)
63
+ );
64
+ await stream.end();
65
+ }
66
+ } else if (args.output) {
67
+ if (args.verbose) console.log('Writing', args.output);
68
+ await reflib.writeFile(args.output, refs);
69
+ }
70
+
71
+ if (args.verbose) console.log('All done');
72
+ process.exit(0);
73
+ }
74
+ // }}}
75
+ // Action: Help {{{
76
+ if (args.help) {
77
+ console.log(help);
78
+ process.exit(0);
79
+ }
80
+ // }}}
81
+ // Action: Unknown {{{
82
+ console.warn('Nothing to do');
83
+ console.log(help);
84
+ process.exit(1);
85
+ // }}}
package/modules/bibtex.js CHANGED
@@ -23,9 +23,9 @@ const MODES = {
23
23
  * @param {Boolean} [options.recNumberNumeric=true] Only process the BibTeX ID into a recNumber if its a finite numeric, otherwise disguard
24
24
  * @param {Boolean} [options.recNumberRNPrefix=true] Accept `RN${NUMBER}` as recNumber if present
25
25
  * @param {Boolean} [options.recNumberKey=true] If the reference key cannot be otherwise parsed store it in `key<String>` instead
26
- * @param {Boolean} [options.omitUnkown=false] If true, only keep known reconised fields
27
26
  * @param {String} [options.fallbackType='unkown'] Reflib fallback type if the incoming type is unrecognised or unsupported
28
27
  * @param {Set<String>} [options.fieldsOverwrite] Set of field names where the value is clobbered rather than appended if discovered more than once
28
+ * @param {Boolean} [options.preserveUnknownKeys=true] Retain keys we do not have a direct lookup for in the output object
29
29
  *
30
30
  * @returns {Object} A readable stream analogue defined in `modules/interface.js`
31
31
  */
@@ -34,9 +34,9 @@ export function readStream(stream, options) {
34
34
  recNumberNumeric: true,
35
35
  recNumberRNPrefix: true,
36
36
  recNumberKey: true,
37
- omitUnknown: false,
38
37
  fallbackType: 'unknown',
39
38
  fieldsOverwrite: new Set(['type']),
39
+ preserveUnkownKeys: true,
40
40
  ...options,
41
41
  };
42
42
 
@@ -100,7 +100,13 @@ export function readStream(stream, options) {
100
100
  )
101
101
  ) {
102
102
  mode = MODES.FIELDS;
103
- if (ref[state.field] !== undefined && settings.fieldsOverwrite.has(state.field)) { // Already have content - and we should overwrite
103
+ if (// Already have content - and we should overwrite
104
+ ref[state.field] !== undefined
105
+ && (
106
+ settings.preserveUnkownKeys
107
+ || settings.fieldsOverwrite.has(state.field)
108
+ )
109
+ ) {
104
110
  ref[state.field] = unescape(match.groups.value);
105
111
  } else if (ref[state.field] !== undefined) { // Already have content - append
106
112
  ref[state.field] += '\n' + unescape(match.groups.value);
@@ -142,13 +148,13 @@ export function tidyRef(ref, settings) {
142
148
  return rlType
143
149
  ? [key, rlType.rl] // Can translate incoming type to Reflib type
144
150
  : [key, settings.fallbackType] // Unknown Reflib type varient
145
- } else if (settings.omitUnkown && !rlField) { // Omit unknown fields
151
+ } else if (!settings.preserveUnkownKeys && !rlField) { // Omit unknown fields
146
152
  return;
147
153
  } else if (rlField && rlField.array) { // Field needs array casting
148
154
  return [rlField.rl, val.split(/\n*\s+and\s+/)];
149
155
  } else if (rlField && rlField.rl) { // Known BT field but different RL field
150
156
  return [rlField.rl, val];
151
- } else if (!settings.omitUnkown) { // Everything else - add field
157
+ } else if (settings.preserveUnkownKeys) { // Everything else - add field
152
158
  return [key, val];
153
159
  }
154
160
  })
@@ -194,10 +200,11 @@ export function escape(str) {
194
200
  * @param {Object} [options] Additional options to use when parsing
195
201
  * @param {string} [options.defaultType='Misc'] Default citation type to assume when no other type is specified
196
202
  * @param {string} [options.delimeter='\r'] How to split multi-line items
197
- * @param {Boolean} [options.omitUnkown=false] If true, only keep known reconised fields
198
203
  * @param {Set} [options.omitFields] Set of special fields to always omit, either because we are ignoring or because we have special treatment for them
204
+ * @param {Boolean} [options.keyForce=true] Force a unique ID to exist if we don't already have one for each reference
199
205
  * @param {Boolean} [options.recNumberRNPrefix=true] Rewrite recNumber fields as `RN${NUMBER}`
200
206
  * @param {Boolean} [options.recNumberKey=true] If the reference `recNumber` is empty use `key<String>` instead
207
+ * @param {Boolean} [options.preserveUnknownKeys=true] Output keys we do not have a direct lookup for in the output object
201
208
  *
202
209
  * @returns {Object} A writable stream analogue defined in `modules/interface.js`
203
210
  */
@@ -205,10 +212,11 @@ export function writeStream(stream, options) {
205
212
  let settings = {
206
213
  defaultType: 'Misc',
207
214
  delimeter: '\n',
208
- omitUnkown: false,
209
- omitFields: new Set(['recNumber', 'type']),
215
+ omitFields: new Set(['key', 'recNumber', 'type']),
216
+ keyForce: true,
210
217
  recNumberRNPrefix: true,
211
218
  recNumberKey: true,
219
+ preserveUnkownKeys: true,
212
220
  ...options,
213
221
  };
214
222
 
@@ -217,12 +225,9 @@ export function writeStream(stream, options) {
217
225
  return Promise.resolve();
218
226
  },
219
227
  write: ref => {
220
- if (!ref.key) {
221
- ref.key = generateCitationKey(ref);
222
- }
223
- // console.log("Here is the id",ref.key)
224
228
  // Fetch Reflib type definition
225
- let rlType = (ref.type || settings.defaultType) && translations.types.rlMap.get(ref.type.toLowerCase());
229
+ ref.type ||= settings.defaultType;
230
+ let rlType = translations.types.rlMap.get(ref.type.toLowerCase());
226
231
  let btType = rlType?.bt || settings.defaultType;
227
232
 
228
233
  stream.write(
@@ -231,6 +236,7 @@ export function writeStream(stream, options) {
231
236
  ref.recNumber && settings.recNumberRNPrefix ? `RN${ref.recNumber},`
232
237
  : ref.recNumber ? `${ref.recNumber},`
233
238
  : ref.key ? `${ref.key},`
239
+ : settings.keyForce ? `${generateCitationKey(ref)},`
234
240
  : ''
235
241
  ) + '\n'
236
242
  + Object.entries(ref)
@@ -241,7 +247,7 @@ export function writeStream(stream, options) {
241
247
  .reduce((buf, [rawKey, rawVal], keyIndex, keys) => {
242
248
  // Fetch Reflib field definition
243
249
  let rlField = translations.fields.rlMap.get(rawKey)
244
- if (!rlField && settings.omitUnkown) return buf; // Unknown field mapping - skip if were omitting unknown fields
250
+ if (!rlField && !settings.preserveUnkownKeys) return buf; // Unknown field mapping - skip if were omitting unknown fields
245
251
 
246
252
  let key = rlField ? rlField.bt : rawKey; // Use Reflib->BibTeX field mapping if we have one, otherwise use raw key
247
253
  let val = escape( // Escape input value, either as an Array via join or as a flat string
package/package.json CHANGED
@@ -1,7 +1,10 @@
1
1
  {
2
2
  "name": "@iebh/reflib",
3
- "version": "2.7.0",
3
+ "version": "2.8.0",
4
4
  "description": "Reference / Citation reference library utilities",
5
+ "bin": {
6
+ "reflib": "./app.js"
7
+ },
5
8
  "scripts": {
6
9
  "lint": "eslint",
7
10
  "test": "testa",
@@ -31,7 +34,7 @@
31
34
  "homepage": "https://github.com/IEBH/Reflib",
32
35
  "enginesStrict": true,
33
36
  "engines": {
34
- "node": "^>=16.6.0"
37
+ "node": ">=16.6.0"
35
38
  },
36
39
  "type": "module",
37
40
  "exports": {
@@ -59,6 +62,7 @@
59
62
  "@momsfriendlydevco/eslint-config": "^2.3.1",
60
63
  "@momsfriendlydevco/testa": "^1.1.2",
61
64
  "eslint": "^9.31.0",
65
+ "execa": "^9.6.1",
62
66
  "nodemon": "^3.1.9",
63
67
  "temp": "^0.9.4",
64
68
  "vite-plugin-replace": "^0.1.1"
@@ -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
+ }