gitsheets 0.20.0 → 0.21.3
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/bin/cli.js +36 -14
- package/commands/edit.js +16 -10
- package/commands/normalize.js +17 -3
- package/commands/read.js +3 -8
- package/commands/upsert.js +35 -2
- package/lib/Sheet.js +26 -6
- package/lib/path/Template.js +8 -7
- package/package.json +7 -5
package/bin/cli.js
CHANGED
|
@@ -2,18 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
// setup logger
|
|
5
|
-
const logger = require('winston')
|
|
6
|
-
|
|
5
|
+
const logger = require('winston');
|
|
6
|
+
const loggerConsole = new logger.transports.Console({
|
|
7
|
+
level: process.env.DEBUG ? 'debug' : 'info',
|
|
8
|
+
format: logger.format.combine(
|
|
9
|
+
logger.format.colorize(),
|
|
10
|
+
logger.format.prettyPrint(),
|
|
11
|
+
logger.format.splat(),
|
|
12
|
+
logger.format.simple(),
|
|
13
|
+
),
|
|
7
14
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
15
|
+
// all logger output to STDERR
|
|
16
|
+
stderrLevels: Object.keys(require('winston/lib/winston/config').cli.levels),
|
|
17
|
+
});
|
|
18
|
+
logger.add(loggerConsole);
|
|
11
19
|
|
|
12
|
-
|
|
13
|
-
// all logger output to STDERR
|
|
14
|
-
for (const level in logger.levels) {
|
|
15
|
-
logger.default.transports.console.stderrLevels[level] = true;
|
|
16
|
-
}
|
|
20
|
+
module.exports = { logger };
|
|
17
21
|
|
|
18
22
|
|
|
19
23
|
// route command line
|
|
@@ -25,15 +29,33 @@ require('yargs')
|
|
|
25
29
|
default: false,
|
|
26
30
|
global: true,
|
|
27
31
|
})
|
|
32
|
+
.option('q', {
|
|
33
|
+
alias: 'quiet',
|
|
34
|
+
type: 'boolean',
|
|
35
|
+
default: false,
|
|
36
|
+
global: true,
|
|
37
|
+
})
|
|
28
38
|
.check(function (argv) {
|
|
29
39
|
if (argv.debug) {
|
|
30
|
-
|
|
40
|
+
loggerConsole.level = 'debug';
|
|
31
41
|
} else if (argv.quiet) {
|
|
32
|
-
|
|
42
|
+
loggerConsole.level = 'error';
|
|
33
43
|
}
|
|
34
44
|
|
|
35
45
|
return true;
|
|
36
46
|
})
|
|
37
|
-
.commandDir('../commands')
|
|
47
|
+
.commandDir('../commands', { exclude: /\.test\.js$/ })
|
|
38
48
|
.demandCommand()
|
|
39
|
-
.
|
|
49
|
+
.showHelpOnFail(false, 'Specify --help for available options')
|
|
50
|
+
.fail((msg, err) => {
|
|
51
|
+
logger.error(msg || err.message);
|
|
52
|
+
|
|
53
|
+
if (err) {
|
|
54
|
+
logger.debug(err.stack);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
process.exit(1);
|
|
58
|
+
})
|
|
59
|
+
.strict()
|
|
60
|
+
.help()
|
|
61
|
+
.argv;
|
package/commands/edit.js
CHANGED
|
@@ -19,6 +19,8 @@ exports.builder = {
|
|
|
19
19
|
exports.handler = async function edit({ recordPath, resumePath, encoding }) {
|
|
20
20
|
const fs = require('fs');
|
|
21
21
|
const { spawn } = require('child_process');
|
|
22
|
+
const path = require('path');
|
|
23
|
+
const tmp = require('tmp');
|
|
22
24
|
const TOML = require('@iarna/toml');
|
|
23
25
|
const Repository = require('../lib/Repository.js');
|
|
24
26
|
const Sheet = require('../lib/Sheet.js')
|
|
@@ -26,17 +28,21 @@ exports.handler = async function edit({ recordPath, resumePath, encoding }) {
|
|
|
26
28
|
const git = await repo.getGit();
|
|
27
29
|
|
|
28
30
|
// open record
|
|
29
|
-
|
|
31
|
+
let recordToml = fs.readFileSync(resumePath || recordPath, encoding);
|
|
30
32
|
|
|
31
|
-
//
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
// try to parse and format
|
|
34
|
+
try {
|
|
35
|
+
const record = TOML.parse(recordToml);
|
|
36
|
+
recordToml = Sheet.stringifyRecord(record);
|
|
37
|
+
} catch (err) {
|
|
38
|
+
console.warn(`Failed to parse opened record:\n${err}`);
|
|
39
|
+
}
|
|
38
40
|
|
|
39
|
-
|
|
41
|
+
// get temp path
|
|
42
|
+
const { name: tempFilePath } = tmp.fileSync({
|
|
43
|
+
prefix: path.basename(recordPath, '.toml'),
|
|
44
|
+
postfix: '.toml',
|
|
45
|
+
discardDescriptor: true,
|
|
40
46
|
});
|
|
41
47
|
|
|
42
48
|
// populate temp path
|
|
@@ -67,7 +73,7 @@ exports.handler = async function edit({ recordPath, resumePath, encoding }) {
|
|
|
67
73
|
try {
|
|
68
74
|
editedRecord = TOML.parse(editedToml);
|
|
69
75
|
} catch (err) {
|
|
70
|
-
console.error(`Failed to parse record:\n${err}`);
|
|
76
|
+
console.error(`Failed to parse edited record:\n${err}`);
|
|
71
77
|
console.error(`To resume editing, run: git sheet edit ${recordPath} ${tempFilePath}`);
|
|
72
78
|
process.exit(1);
|
|
73
79
|
}
|
package/commands/normalize.js
CHANGED
|
@@ -54,9 +54,23 @@ exports.handler = async function query({
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
// loop through all records and re-upsert
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
try {
|
|
58
|
+
for await (const record of sheet.query()) {
|
|
59
|
+
const originalPath = record[Symbol.for('gitsheets-path')];
|
|
60
|
+
logger.info(`rewriting ${path.join(root, prefix, sheetName, originalPath)}.toml`);
|
|
61
|
+
const { path: normalizedPath } = await sheet.upsert(record);
|
|
62
|
+
|
|
63
|
+
if (normalizedPath !== originalPath) {
|
|
64
|
+
logger.warn(`^- moved to ${path.join(root, prefix, sheetName, normalizedPath)}.toml`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
} catch (err) {
|
|
68
|
+
if (err.constructor.name == 'TomlError') {
|
|
69
|
+
logger.error(`failed to parse ${path.join(root, prefix, err.file)}\n${err.message}`);
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
throw err;
|
|
60
74
|
}
|
|
61
75
|
}
|
|
62
76
|
|
package/commands/read.js
CHANGED
|
@@ -3,7 +3,7 @@ exports.desc = 'Read a record, converting to desired format';
|
|
|
3
3
|
exports.builder = {
|
|
4
4
|
'record-path': {
|
|
5
5
|
type: 'string',
|
|
6
|
-
describe: 'The path to a record file to
|
|
6
|
+
describe: 'The path to a record file to read',
|
|
7
7
|
demandOption: true,
|
|
8
8
|
},
|
|
9
9
|
encoding: {
|
|
@@ -24,12 +24,7 @@ exports.builder = {
|
|
|
24
24
|
|
|
25
25
|
exports.handler = async function edit({ recordPath, encoding, format, headers }) {
|
|
26
26
|
const fs = require('fs');
|
|
27
|
-
const { spawn } = require('child_process');
|
|
28
27
|
const TOML = require('@iarna/toml');
|
|
29
|
-
const Repository = require('../lib/Repository.js');
|
|
30
|
-
const Sheet = require('../lib/Sheet.js')
|
|
31
|
-
const repo = await Repository.getFromEnvironment({ working: true });
|
|
32
|
-
const git = await repo.getGit();
|
|
33
28
|
|
|
34
29
|
// open record
|
|
35
30
|
const recordToml = fs.readFileSync(recordPath, encoding);
|
|
@@ -64,6 +59,6 @@ async function outputCsv(record, { headers = true, delimiter = ',' } = {}) {
|
|
|
64
59
|
}
|
|
65
60
|
|
|
66
61
|
async function outputToml(record) {
|
|
67
|
-
const
|
|
68
|
-
console.log(`${
|
|
62
|
+
const Sheet = require('../lib/Sheet.js')
|
|
63
|
+
console.log(`${Sheet.stringifyRecord(record)}`);
|
|
69
64
|
}
|
package/commands/upsert.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const TOML = require('@iarna/toml');
|
|
3
3
|
const { parse: csvParse } = require('fast-csv');
|
|
4
|
+
const deepmerge = require('deepmerge');
|
|
4
5
|
|
|
5
6
|
const inputFormats = {
|
|
6
7
|
json: readJsonFile,
|
|
@@ -46,6 +47,11 @@ exports.builder = {
|
|
|
46
47
|
type: 'boolean',
|
|
47
48
|
default: false,
|
|
48
49
|
},
|
|
50
|
+
'patch-existing': {
|
|
51
|
+
describe: 'For existing records, patch in provided values so that additional properties not included in the input are preserved',
|
|
52
|
+
type: 'boolean',
|
|
53
|
+
default: false,
|
|
54
|
+
},
|
|
49
55
|
};
|
|
50
56
|
|
|
51
57
|
exports.handler = async function upsert({
|
|
@@ -57,6 +63,7 @@ exports.handler = async function upsert({
|
|
|
57
63
|
encoding,
|
|
58
64
|
attachments = null,
|
|
59
65
|
deleteMissing,
|
|
66
|
+
patchExisting,
|
|
60
67
|
...argv
|
|
61
68
|
}) {
|
|
62
69
|
const logger = require('../lib/logger.js');
|
|
@@ -96,7 +103,13 @@ exports.handler = async function upsert({
|
|
|
96
103
|
|
|
97
104
|
|
|
98
105
|
// clear sheet
|
|
106
|
+
let inputSheet = sheet;
|
|
107
|
+
|
|
99
108
|
if (deleteMissing) {
|
|
109
|
+
// re-open input sheet
|
|
110
|
+
inputSheet = await sheet.clone();
|
|
111
|
+
|
|
112
|
+
// clear target sheet
|
|
100
113
|
await sheet.clear();
|
|
101
114
|
}
|
|
102
115
|
|
|
@@ -113,8 +126,28 @@ exports.handler = async function upsert({
|
|
|
113
126
|
|
|
114
127
|
|
|
115
128
|
// upsert record(s) into sheet
|
|
116
|
-
for await (
|
|
117
|
-
|
|
129
|
+
for await (let inputRecord of inputRecords) {
|
|
130
|
+
|
|
131
|
+
if (patchExisting) {
|
|
132
|
+
// TODO: move more of this logic inside Sheet class
|
|
133
|
+
|
|
134
|
+
// fetch existing record from inputSheet
|
|
135
|
+
const inputRecordPath = await inputSheet.pathForRecord(await inputSheet.normalizeRecord(inputRecord));
|
|
136
|
+
|
|
137
|
+
if (inputRecordPath) {
|
|
138
|
+
const { root: inputSheetRoot } = await inputSheet.getCachedConfig();
|
|
139
|
+
|
|
140
|
+
// existing record find, merge
|
|
141
|
+
const existingBlob = await inputSheet.dataTree.getChild(`${path.join(inputSheetRoot, inputRecordPath)}.toml`);
|
|
142
|
+
|
|
143
|
+
if (existingBlob) {
|
|
144
|
+
const existingRecord = await inputSheet.readRecord(existingBlob);
|
|
145
|
+
inputRecord = deepmerge(inputRecord, existingRecord);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const { blob: outputBlob, path: outputPath } = await sheet.upsert(inputRecord, { patchExisting });
|
|
118
151
|
console.log(`${outputBlob.hash}\t${outputPath}`);
|
|
119
152
|
|
|
120
153
|
if (attachments) {
|
package/lib/Sheet.js
CHANGED
|
@@ -118,9 +118,20 @@ class Sheet extends Configurable
|
|
|
118
118
|
async readRecord (blob, path = null) {
|
|
119
119
|
const cache = this.#recordCache.get(blob.hash);
|
|
120
120
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
121
|
+
let record;
|
|
122
|
+
|
|
123
|
+
if (cache) {
|
|
124
|
+
record = v8.deserialize(cache);
|
|
125
|
+
} else {
|
|
126
|
+
const toml = await blob.read();
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
record = TOML.parse(toml);
|
|
130
|
+
} catch (err) {
|
|
131
|
+
err.file = path;
|
|
132
|
+
throw err;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
124
135
|
|
|
125
136
|
// annotate with gitsheets keys
|
|
126
137
|
record[RECORD_SHEET_KEY] = this.name;
|
|
@@ -153,14 +164,14 @@ class Sheet extends Configurable
|
|
|
153
164
|
const pathTemplate = PathTemplate.fromString(pathTemplateString);
|
|
154
165
|
const sheetDataTree = await this.dataTree.getSubtree(sheetRoot);
|
|
155
166
|
|
|
156
|
-
BLOBS: for await (const blob of pathTemplate.queryTree(sheetDataTree, query)) {
|
|
157
|
-
const record = await this.readRecord(blob);
|
|
167
|
+
BLOBS: for await (const { blob, path: blobPath } of pathTemplate.queryTree(sheetDataTree, query)) {
|
|
168
|
+
const record = await this.readRecord(blob, blobPath);
|
|
158
169
|
|
|
159
170
|
if (!queryMatches(query, record)) {
|
|
160
171
|
continue BLOBS;
|
|
161
172
|
}
|
|
162
173
|
|
|
163
|
-
record[RECORD_PATH_KEY] = pathTemplate.render(record);
|
|
174
|
+
record[RECORD_PATH_KEY] = blobPath || pathTemplate.render(record);
|
|
164
175
|
|
|
165
176
|
yield record;
|
|
166
177
|
}
|
|
@@ -277,6 +288,15 @@ class Sheet extends Configurable
|
|
|
277
288
|
return this.dataTree.writeChild(root, this.dataTree.repo.createTree());
|
|
278
289
|
}
|
|
279
290
|
|
|
291
|
+
async clone () {
|
|
292
|
+
return new Sheet({
|
|
293
|
+
workspace: this.workspace,
|
|
294
|
+
name: this.name,
|
|
295
|
+
dataTree: await this.dataTree.clone(),
|
|
296
|
+
configPath: this.configPath,
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
|
|
280
300
|
async upsert (record) {
|
|
281
301
|
const {
|
|
282
302
|
root: sheetRoot,
|
package/lib/path/Template.js
CHANGED
|
@@ -50,7 +50,7 @@ class Template
|
|
|
50
50
|
return recordPath.join('/');
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
async* queryTree (tree, query, depth = 0) {
|
|
53
|
+
async* queryTree (tree, query, pathPrefix = '', depth = 0) {
|
|
54
54
|
const numComponents = this.#components.length;
|
|
55
55
|
|
|
56
56
|
if (!tree) {
|
|
@@ -68,7 +68,7 @@ class Template
|
|
|
68
68
|
const child = await currentTree.getChild(`${nextName}.toml`);
|
|
69
69
|
|
|
70
70
|
if (child) {
|
|
71
|
-
yield child;
|
|
71
|
+
yield { path: path.join(pathPrefix, nextName), blob: child };
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
// absolute match on a leaf, we're done with this query
|
|
@@ -97,8 +97,9 @@ class Template
|
|
|
97
97
|
continue;
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
|
|
101
|
-
|
|
100
|
+
const childName = childPath.substr(0, childPath.length - 5);
|
|
101
|
+
attachmentsPrefix = `${childName}/`;
|
|
102
|
+
yield { path: path.join(pathPrefix, childName), blob: child };
|
|
102
103
|
}
|
|
103
104
|
|
|
104
105
|
return;
|
|
@@ -117,14 +118,14 @@ class Template
|
|
|
117
118
|
} else {
|
|
118
119
|
// each tree in current tree could contain matching records
|
|
119
120
|
const children = await currentTree.getChildren();
|
|
120
|
-
for (const
|
|
121
|
-
const child = children[
|
|
121
|
+
for (const childPath in children) {
|
|
122
|
+
const child = children[childPath];
|
|
122
123
|
|
|
123
124
|
if (!child.isTree) {
|
|
124
125
|
continue;
|
|
125
126
|
}
|
|
126
127
|
|
|
127
|
-
yield* this.queryTree(child, query, i+1);
|
|
128
|
+
yield* this.queryTree(child, query, path.join(pathPrefix, childPath), i+1);
|
|
128
129
|
}
|
|
129
130
|
|
|
130
131
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gitsheets",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.3",
|
|
4
4
|
"description": "A toolkit for using a git repository to store low-volume, high-touch, human-scale data",
|
|
5
5
|
"main": "lib/GitSheets.js",
|
|
6
6
|
"scripts": {
|
|
@@ -22,10 +22,11 @@
|
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"@iarna/toml": "^2.2.5",
|
|
24
24
|
"csv-parser": "^2.3.5",
|
|
25
|
-
"
|
|
25
|
+
"deepmerge": "^4.2.2",
|
|
26
|
+
"fast-csv": "^4.3.6",
|
|
26
27
|
"fast-json-patch": "^2.2.1",
|
|
27
28
|
"get-stream": "^5.2.0",
|
|
28
|
-
"hologit": "^0.
|
|
29
|
+
"hologit": "^0.40.5",
|
|
29
30
|
"http-assert": "^1.4.1",
|
|
30
31
|
"koa": "^2.13.1",
|
|
31
32
|
"koa-bodyparser": "^4.3.0",
|
|
@@ -35,15 +36,16 @@
|
|
|
35
36
|
"rfc6902": "^4.0.1",
|
|
36
37
|
"sort-keys": "^4.2.0",
|
|
37
38
|
"streaming-json-stringify": "^3.1.0",
|
|
39
|
+
"tmp": "^0.2.1",
|
|
38
40
|
"to-readable-stream": "^2.1.0",
|
|
39
|
-
"winston": "^
|
|
41
|
+
"winston": "^3.3.3",
|
|
40
42
|
"yargs": "^13.3.2"
|
|
41
43
|
},
|
|
42
44
|
"devDependencies": {
|
|
43
45
|
"common-tags": "^1.8.0",
|
|
44
46
|
"del": "^5.1.0",
|
|
45
47
|
"into-stream": "^5.1.1",
|
|
46
|
-
"jest": "^
|
|
48
|
+
"jest": "^26.6.3",
|
|
47
49
|
"make-dir": "^3.1.0",
|
|
48
50
|
"supertest": "^4.0.2"
|
|
49
51
|
}
|