votive 0.0.5 → 0.0.7
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 +2 -1
- package/lib/bundle.js +36 -10
- package/lib/createDatabase.js +146 -50
- package/lib/readAbstracts.js +1 -1
- package/lib/readBuffers.js +59 -0
- package/lib/readFilePaths.js +59 -0
- package/lib/readFolders.js +15 -5
- package/lib/readSources.js +84 -23
- package/lib/runJobs.js +28 -8
- package/lib/sqlite.js +76 -0
- package/lib/utils/index.js +6 -2
- package/lib/writeDestinations.js +19 -4
- package/package.json +1 -1
- package/tests/.votive.db +0 -0
- package/tests/index.js +24 -7
package/README.md
CHANGED
package/lib/bundle.js
CHANGED
|
@@ -7,6 +7,7 @@ import writeDestinations from "./writeDestinations.js"
|
|
|
7
7
|
import { default as createDatabase } from "./createDatabase.js"
|
|
8
8
|
|
|
9
9
|
/** @import {Database} from "./createDatabase.js" */
|
|
10
|
+
/** @import {ParsedPath} from "node:path" */
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* @typedef {object} VotivePlugin
|
|
@@ -38,6 +39,7 @@ import { default as createDatabase } from "./createDatabase.js"
|
|
|
38
39
|
/**
|
|
39
40
|
* @typedef {object} ProcessorRead
|
|
40
41
|
* @property {ReadPath} [path]
|
|
42
|
+
* @property {ReadURL} [url]
|
|
41
43
|
* @property {ReadText} [text]
|
|
42
44
|
* @property {ReadAbstract} [abstract]
|
|
43
45
|
* @property {ReadFolder} [folder]
|
|
@@ -49,23 +51,31 @@ import { default as createDatabase } from "./createDatabase.js"
|
|
|
49
51
|
* @param {string} filePath
|
|
50
52
|
* @param {Database} database
|
|
51
53
|
* @param {VotiveConfig} config
|
|
52
|
-
* @returns {
|
|
54
|
+
* @returns {Promise<ReadTextResult>}
|
|
55
|
+
*/
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Reads a the response from a URL and returns arbitrary data.
|
|
59
|
+
* @callback ReadURL
|
|
60
|
+
* @param {Response} response
|
|
61
|
+
* @returns {object}
|
|
53
62
|
*/
|
|
54
63
|
|
|
55
64
|
/**
|
|
56
65
|
* @callback ReadText
|
|
57
66
|
* @param {string} text
|
|
58
67
|
* @param {string} filePath
|
|
68
|
+
* @param {string} destinationPath
|
|
59
69
|
* @param {Database} database
|
|
60
70
|
* @param {VotiveConfig} config
|
|
61
|
-
* @returns {ReadTextResult
|
|
71
|
+
* @returns {ReadTextResult}
|
|
62
72
|
*/
|
|
63
73
|
|
|
64
74
|
/**
|
|
65
75
|
* @typedef {object} ReadTextResult
|
|
66
76
|
* @property {Jobs} [jobs]
|
|
67
|
-
* @property {object}
|
|
68
|
-
* @property {Abstract}
|
|
77
|
+
* @property {object} metadata
|
|
78
|
+
* @property {Abstract} abstract
|
|
69
79
|
*/
|
|
70
80
|
|
|
71
81
|
/**
|
|
@@ -100,9 +110,10 @@ import { default as createDatabase } from "./createDatabase.js"
|
|
|
100
110
|
|
|
101
111
|
/**
|
|
102
112
|
* @callback ReadFolder
|
|
103
|
-
* @param {object}
|
|
113
|
+
* @param {object} folder
|
|
104
114
|
* @param {Database} database
|
|
105
115
|
* @param {VotiveConfig} config
|
|
116
|
+
* @param {boolean} isRoot
|
|
106
117
|
* @returns {{ jobs?: Jobs, destinations?: Destination[] }}
|
|
107
118
|
*/
|
|
108
119
|
|
|
@@ -125,7 +136,7 @@ import { default as createDatabase } from "./createDatabase.js"
|
|
|
125
136
|
* @param {object} destination
|
|
126
137
|
* @param {Database} database
|
|
127
138
|
* @param {VotiveConfig} config
|
|
128
|
-
* @returns {{ data: string, encoding?: BufferEncoding = 'utf-8' }}
|
|
139
|
+
* @returns {{ data: string, buffer: Buffer, encoding?: BufferEncoding = 'utf-8' }}
|
|
129
140
|
*/
|
|
130
141
|
|
|
131
142
|
/**
|
|
@@ -140,14 +151,22 @@ import { default as createDatabase } from "./createDatabase.js"
|
|
|
140
151
|
* Run a job.
|
|
141
152
|
* @typedef {object} Job
|
|
142
153
|
* @property {object} data
|
|
143
|
-
* @property {
|
|
144
|
-
* @property {string} [
|
|
154
|
+
* @property {"text" | "blob" | "json"} format
|
|
155
|
+
* @property {string} [syntax]
|
|
156
|
+
*/
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* @typedef {object} PathInfo
|
|
160
|
+
* @property {string[]} dir
|
|
161
|
+
* @property {ParsedPath["name"]} name
|
|
162
|
+
* @property {ParsedPath["ext"]} ext
|
|
163
|
+
* @property {boolean} [inRootDir]
|
|
145
164
|
*/
|
|
146
165
|
|
|
147
166
|
/**
|
|
148
167
|
* @callback Router
|
|
149
|
-
* @param {
|
|
150
|
-
* @returns {
|
|
168
|
+
* @param {PathInfo} path
|
|
169
|
+
* @returns {PathInfo | false | undefined}
|
|
151
170
|
*/
|
|
152
171
|
|
|
153
172
|
/**
|
|
@@ -172,6 +191,7 @@ import { default as createDatabase } from "./createDatabase.js"
|
|
|
172
191
|
* @param {Database | undefined} [cache]
|
|
173
192
|
*/
|
|
174
193
|
async function bundle(config, cache) {
|
|
194
|
+
// TODO: Ensure all cached destinations exist as expected
|
|
175
195
|
|
|
176
196
|
// Map out all processors
|
|
177
197
|
const processors = config.plugins
|
|
@@ -213,9 +233,15 @@ async function bundle(config, cache) {
|
|
|
213
233
|
...foldersJobs
|
|
214
234
|
], config, database)
|
|
215
235
|
|
|
236
|
+
|
|
237
|
+
await writeDestinations(config, database)
|
|
238
|
+
|
|
216
239
|
// Back up database (only if in-memory first run)
|
|
217
240
|
await database.saveDB()
|
|
218
241
|
|
|
242
|
+
const stale = database.getStaleDestinations()
|
|
243
|
+
if(stale.length > 0) await bundle(config, cache)
|
|
244
|
+
|
|
219
245
|
return database
|
|
220
246
|
}
|
|
221
247
|
|
package/lib/createDatabase.js
CHANGED
|
@@ -2,6 +2,7 @@ import { DatabaseSync, backup } from "node:sqlite"
|
|
|
2
2
|
import { splitURL, checkFile } from "./utils/index.js"
|
|
3
3
|
import { statSync } from "node:fs"
|
|
4
4
|
import path from "node:path"
|
|
5
|
+
import createStatement from "./sqlite.js"
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* @typedef {ReturnType<createDatabase>} Database
|
|
@@ -35,7 +36,7 @@ function createDatabase(dbPath = ".votive.db") {
|
|
|
35
36
|
* @param {string} urlPath
|
|
36
37
|
*/
|
|
37
38
|
database.createFolder = (folderPath, urlPath) => {
|
|
38
|
-
return store.get`INSERT INTO folders (folderPath, urlPath) VALUES :${folderPath} :${urlPath} RETURNING *`
|
|
39
|
+
return store.get`INSERT OR IGNORE INTO folders (folderPath, urlPath) VALUES :${folderPath} :${urlPath} RETURNING *`
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
const createSource = databaseSync.prepare(`INSERT INTO sources (path, destination, lastModified) VALUES (?, ?, ?)`)
|
|
@@ -66,12 +67,12 @@ function createDatabase(dbPath = ".votive.db") {
|
|
|
66
67
|
// These statements are cached, no need to refactor
|
|
67
68
|
const createDest = databaseSync.prepare(`INSERT INTO destinations (path, dir, syntax, stale, abstract) VALUES (?, ?, ?, 1, ?)`)
|
|
68
69
|
const updateDest = databaseSync.prepare(`UPDATE destinations SET abstract = ? WHERE path = ?`)
|
|
69
|
-
const createMeta = databaseSync.prepare(`INSERT INTO metadata (destination, label, value, type) VALUES (?, ?, ?, ?)`)
|
|
70
|
+
const createMeta = databaseSync.prepare(`INSERT OR REPLACE INTO metadata (destination, label, value, type) VALUES (?, ?, ?, ?)`)
|
|
70
71
|
const updateMeta = databaseSync.prepare(`UPDATE metadata SET value = ? WHERE destination = ? AND label = ?`)
|
|
71
72
|
const getDepends = databaseSync.prepare(`SELECT * FROM dependencies WHERE destination = ? AND property = ?`)
|
|
72
73
|
const getDependenciesByDestination = databaseSync.prepare(`SELECT * FROM dependencies WHERE destination = ?`)
|
|
73
74
|
const getAllDeps = databaseSync.prepare(`SELECT * FROM dependencies`)
|
|
74
|
-
const staleDepen = databaseSync.prepare(`UPDATE destinations SET stale = 1 WHERE path =
|
|
75
|
+
const staleDepen = databaseSync.prepare(`UPDATE destinations SET stale = 1 WHERE path = ? RETURNING *`)
|
|
75
76
|
const freshDepen = databaseSync.prepare(`UPDATE destinations SET stale = 0 WHERE path = ? RETURNING *`)
|
|
76
77
|
const staleDescendents = databaseSync.prepare(`UPDATE destinations SET stale = 1 WHERE path LIKE ? RETURNING *`)
|
|
77
78
|
const getAllSettings = databaseSync.prepare(`SELECT * FROM settings`)
|
|
@@ -97,14 +98,21 @@ function createDatabase(dbPath = ".votive.db") {
|
|
|
97
98
|
* @param {string} params.syntax - Destination abstract syntax
|
|
98
99
|
*/
|
|
99
100
|
database.createOrUpdateDestination = ({ metadata, ...dest }) => {
|
|
100
|
-
const
|
|
101
|
+
const dir = dest.path && splitURL(dest.path)
|
|
101
102
|
const params = Object.keys(metadata)
|
|
102
103
|
if (dest.abstract) params.push("abstract")
|
|
103
104
|
const extant = database.getDestinationIndependently(dest.path, params)
|
|
104
105
|
|
|
105
106
|
if (!extant) {
|
|
106
107
|
createDest.get(dest.path, dir, dest.syntax, JSON.stringify(dest.abstract))
|
|
107
|
-
Object.entries(metadata).map(([k, v]) =>
|
|
108
|
+
Object.entries(metadata).map(([k, v]) => {
|
|
109
|
+
const type = typeof v
|
|
110
|
+
const value = type === "object"
|
|
111
|
+
? JSON.stringify(v)
|
|
112
|
+
: v
|
|
113
|
+
|
|
114
|
+
createMeta.get(dest.path, k, value, typeof v)
|
|
115
|
+
})
|
|
108
116
|
return
|
|
109
117
|
}
|
|
110
118
|
|
|
@@ -121,7 +129,7 @@ function createDatabase(dbPath = ".votive.db") {
|
|
|
121
129
|
cachedMetadata.splice(index, 1)
|
|
122
130
|
if (JSON.stringify(metadata[key]) !== JSON.stringify(extant.metadata[key])) {
|
|
123
131
|
changedMetadata = true
|
|
124
|
-
updateMeta.get(metadata[key], dest.path, key)
|
|
132
|
+
updateMeta.get(JSON.stringify(metadata[key]), dest.path, key)
|
|
125
133
|
const dependencies = getDepends.all(dest.path, key)
|
|
126
134
|
dependencies.forEach(({ dependent }) => {
|
|
127
135
|
staleDepen.get(dependent)
|
|
@@ -158,11 +166,10 @@ function createDatabase(dbPath = ".votive.db") {
|
|
|
158
166
|
return getMetadataByPath.get(path)
|
|
159
167
|
}
|
|
160
168
|
|
|
161
|
-
|
|
162
|
-
database.getMetadataIndependently = (params) => {
|
|
169
|
+
database.getMetadataIndependently = () => {
|
|
163
170
|
return databaseSync.prepare(`
|
|
164
171
|
SELECT * FROM destinations d
|
|
165
|
-
LEFT JOIN metadata m ON d.path = m.destination
|
|
172
|
+
LEFT JOIN metadata m ON d.path = m.destination
|
|
166
173
|
WHERE d.path = ?
|
|
167
174
|
`)
|
|
168
175
|
}
|
|
@@ -174,16 +181,29 @@ function createDatabase(dbPath = ".votive.db") {
|
|
|
174
181
|
`)
|
|
175
182
|
}
|
|
176
183
|
|
|
184
|
+
function castMetadata({ label, value, type }) {
|
|
185
|
+
return [label,
|
|
186
|
+
type === "undefined"
|
|
187
|
+
? undefined
|
|
188
|
+
: type === "boolean"
|
|
189
|
+
? Boolean(value)
|
|
190
|
+
: value
|
|
191
|
+
]
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function parseMetadatas(metadatas) {
|
|
195
|
+
return Object.fromEntries(
|
|
196
|
+
metadatas.map(castMetadata)
|
|
197
|
+
)
|
|
198
|
+
}
|
|
199
|
+
|
|
177
200
|
/**
|
|
178
201
|
* @param {string} path
|
|
179
|
-
* @param {string[]} [params]
|
|
180
202
|
*/
|
|
181
|
-
database.getDestinationIndependently = (path
|
|
203
|
+
database.getDestinationIndependently = (path) => {
|
|
182
204
|
// TODO: Could potentially speed up the next two db queries by using the tag store
|
|
183
205
|
|
|
184
|
-
const metadatas = (
|
|
185
|
-
? database.getMetadataIndependently(params).all(path)
|
|
186
|
-
: [database.getDestinationWithoutMetadata().get(path)]
|
|
206
|
+
const metadatas = database.getMetadataIndependently().all(path)
|
|
187
207
|
|
|
188
208
|
if (!metadatas) return
|
|
189
209
|
|
|
@@ -191,52 +211,94 @@ function createDatabase(dbPath = ".votive.db") {
|
|
|
191
211
|
|
|
192
212
|
if (!first) return
|
|
193
213
|
|
|
194
|
-
const metadata =
|
|
195
|
-
metadatas.map(({ label, value, type }) => {
|
|
196
|
-
const originalValue = type === "undefined"
|
|
197
|
-
? undefined
|
|
198
|
-
: type === "string"
|
|
199
|
-
? String(value)
|
|
200
|
-
: type === "boolean"
|
|
201
|
-
? Boolean(value)
|
|
202
|
-
: typeof value !== "string"
|
|
203
|
-
? value
|
|
204
|
-
: JSON.parse(value)
|
|
205
|
-
return [label, originalValue]
|
|
206
|
-
})
|
|
207
|
-
)
|
|
208
|
-
|
|
214
|
+
const metadata = parseMetadatas(metadatas)
|
|
209
215
|
|
|
210
216
|
const result = {
|
|
217
|
+
abstract: JSON.parse(first.abstract),
|
|
211
218
|
path: first.path,
|
|
212
219
|
dir: first.dir,
|
|
213
220
|
syntax: first.syntax,
|
|
214
221
|
metadata
|
|
215
222
|
}
|
|
216
223
|
|
|
217
|
-
const includeAbstract = params && params.includes("abstract")
|
|
218
|
-
if (includeAbstract) result.abstract = JSON.parse(first.abstract)
|
|
219
|
-
|
|
220
224
|
return result
|
|
221
225
|
}
|
|
222
226
|
|
|
227
|
+
const createDependency = databaseSync.prepare(`INSERT OR IGNORE INTO dependencies (destination, property, dependent) VALUES (?, ?, ?) `)
|
|
228
|
+
const getDependency = databaseSync.prepare("SELECT * FROM dependencies WHERE destination = ? AND property = ?")
|
|
223
229
|
/**
|
|
224
230
|
* @param {string} path
|
|
225
|
-
* @param {string[]} properties
|
|
226
231
|
* @param {string} dependent
|
|
227
232
|
*/
|
|
228
|
-
database.getDestinationDependently = (path,
|
|
229
|
-
const
|
|
230
|
-
|
|
233
|
+
database.getDestinationDependently = (path, dependent) => {
|
|
234
|
+
const result = database.getDestinationIndependently(path)
|
|
235
|
+
|
|
236
|
+
const { abstract, metadata, ...copy } = result
|
|
237
|
+
|
|
238
|
+
copy.metadata = {}
|
|
231
239
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
240
|
+
Object.defineProperty(copy, "abstract", {
|
|
241
|
+
enumerable: true,
|
|
242
|
+
get() {
|
|
243
|
+
createDependency.get(path, "abstract", dependent)
|
|
244
|
+
return abstract
|
|
245
|
+
}
|
|
235
246
|
})
|
|
236
247
|
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
|
|
248
|
+
const keys = Object.keys(metadata)
|
|
249
|
+
|
|
250
|
+
keys.forEach(key => {
|
|
251
|
+
Object.defineProperty(copy.metadata, key, {
|
|
252
|
+
enumerable: true,
|
|
253
|
+
get() {
|
|
254
|
+
createDependency.get(path, key, dependent)
|
|
255
|
+
return metadata[key]
|
|
256
|
+
}
|
|
257
|
+
})
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
return copy
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/** @param {import("./sqlite.js").Query} query */
|
|
265
|
+
database.getDestinations = (query = {}, dependent) => {
|
|
266
|
+
const statement = createStatement(query)
|
|
267
|
+
const results = databaseSync.prepare(statement).all()
|
|
268
|
+
const destinations = Object.values(results.reduce((pv, cv) => {
|
|
269
|
+
if (!pv || !pv[cv.path]) {
|
|
270
|
+
pv[cv.path] = {
|
|
271
|
+
dir: cv.dir,
|
|
272
|
+
path: cv.path,
|
|
273
|
+
syntax: cv.syntax,
|
|
274
|
+
metadata: {}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
Object.defineProperty(pv[cv.path], "abstract",
|
|
278
|
+
{
|
|
279
|
+
enumerable: true,
|
|
280
|
+
get() {
|
|
281
|
+
createDependency.get(cv.path, "abstract", dependent)
|
|
282
|
+
return cv.abstract
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
)
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
Object.defineProperty(pv[cv.path].metadata, cv.label,
|
|
289
|
+
{
|
|
290
|
+
enumerable: true,
|
|
291
|
+
get() {
|
|
292
|
+
createDependency.get(cv.path, cv.label, dependent)
|
|
293
|
+
return castMetadata(cv)[1]
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
return pv
|
|
299
|
+
}, {}))
|
|
300
|
+
|
|
301
|
+
return destinations
|
|
240
302
|
}
|
|
241
303
|
|
|
242
304
|
const selectSource = databaseSync.prepare(`SELECT * FROM sources WHERE path = ?`)
|
|
@@ -285,8 +347,6 @@ function createDatabase(dbPath = ".votive.db") {
|
|
|
285
347
|
LEFT JOIN metadata m on d.path = m.destination
|
|
286
348
|
WHERE stale = 1`
|
|
287
349
|
|
|
288
|
-
console.log({ metadatas })
|
|
289
|
-
|
|
290
350
|
const grouped = metadatas.map((value, index, array) => {
|
|
291
351
|
if (array.slice(0, index)
|
|
292
352
|
.find(prior => prior.path === value.path)
|
|
@@ -298,6 +358,7 @@ function createDatabase(dbPath = ".votive.db") {
|
|
|
298
358
|
dir: value.dir,
|
|
299
359
|
syntax: value.syntax,
|
|
300
360
|
stale: value.stale,
|
|
361
|
+
abstract: JSON.parse(value.abstract),
|
|
301
362
|
metadata: Object.fromEntries(
|
|
302
363
|
array.slice(index)
|
|
303
364
|
.filter(following => following.path === value.path)
|
|
@@ -326,7 +387,7 @@ function createDatabase(dbPath = ".votive.db") {
|
|
|
326
387
|
return grouped
|
|
327
388
|
}
|
|
328
389
|
|
|
329
|
-
const createSetting = databaseSync.prepare(`INSERT INTO settings (destination, label, value, source) VALUES (?, ?, ?, ?) RETURNING *`)
|
|
390
|
+
const createSetting = databaseSync.prepare(`INSERT OR IGNORE INTO settings (destination, label, value, source) VALUES (?, ?, ?, ?) RETURNING *`)
|
|
330
391
|
const selectSetting = databaseSync.prepare(`SELECT * FROM settings WHERE label = ? AND destination = ?`)
|
|
331
392
|
const updateSetting = databaseSync.prepare(`UPDATE settings SET value = ? WHERE destination = ? AND label = ? RETURNING *`)
|
|
332
393
|
const deleteSettings = databaseSync.prepare(`DELETE FROM settings WHERE source = ?`)
|
|
@@ -356,9 +417,14 @@ function createDatabase(dbPath = ".votive.db") {
|
|
|
356
417
|
database.setSetting = (destinationFolder, key, value, source = "") => {
|
|
357
418
|
const extant = selectSetting.get(key, destinationFolder)
|
|
358
419
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
420
|
+
const type = typeof value
|
|
421
|
+
const safeValue = type === "object"
|
|
422
|
+
? JSON.stringify(value)
|
|
423
|
+
: value
|
|
424
|
+
|
|
425
|
+
if (!extant) return createSetting.get(destinationFolder, key, safeValue, source)
|
|
426
|
+
if (extant.value === safeValue) return
|
|
427
|
+
updateSetting.get(safeValue, destinationFolder, key)
|
|
362
428
|
|
|
363
429
|
destinationFolder === ""
|
|
364
430
|
? staleDescendents.all("%")
|
|
@@ -399,6 +465,29 @@ function createDatabase(dbPath = ".votive.db") {
|
|
|
399
465
|
)
|
|
400
466
|
}
|
|
401
467
|
|
|
468
|
+
const createURL = databaseSync.prepare(`INSERT OR IGNORE INTO urls (url, data) VALUES (?, ?) RETURNING *`)
|
|
469
|
+
const getURL = databaseSync.prepare(`SELECT data FROM urls WHERE url = ?`)
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* @param {string} url
|
|
473
|
+
* @param {string} data
|
|
474
|
+
* @param {string} destination
|
|
475
|
+
*/
|
|
476
|
+
database.createURL = (url, data, destination) => {
|
|
477
|
+
const created = createURL.get(url, JSON.stringify(data))
|
|
478
|
+
const staled = staleDepen.get(destination)
|
|
479
|
+
// TODO: Better signal propagation here?
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* @param {string} url
|
|
484
|
+
*/
|
|
485
|
+
database.getURL = (url) => {
|
|
486
|
+
const { data } = getURL.get(url) || {}
|
|
487
|
+
if(!data) return
|
|
488
|
+
return JSON.parse(data)
|
|
489
|
+
}
|
|
490
|
+
|
|
402
491
|
return database
|
|
403
492
|
}
|
|
404
493
|
|
|
@@ -414,7 +503,8 @@ CREATE TABLE IF NOT EXISTS dependencies (
|
|
|
414
503
|
key INTEGER PRIMARY KEY,
|
|
415
504
|
destination STRING NOT NULL,
|
|
416
505
|
property STRING NOT NULL,
|
|
417
|
-
dependent STRING NOT NULL
|
|
506
|
+
dependent STRING NOT NULL,
|
|
507
|
+
UNIQUE(destination, property, dependent)
|
|
418
508
|
);
|
|
419
509
|
|
|
420
510
|
CREATE TABLE IF NOT EXISTS destinations (
|
|
@@ -440,7 +530,13 @@ CREATE TABLE IF NOT EXISTS settings (
|
|
|
440
530
|
destination STRING,
|
|
441
531
|
source STRING,
|
|
442
532
|
label STRING,
|
|
443
|
-
value STRING
|
|
533
|
+
value STRING,
|
|
534
|
+
UNIQUE(destination, label, source)
|
|
535
|
+
);
|
|
536
|
+
|
|
537
|
+
CREATE TABLE IF NOT EXISTS urls (
|
|
538
|
+
url STRING PRIMARY KEY,
|
|
539
|
+
data STRING
|
|
444
540
|
);
|
|
445
541
|
|
|
446
542
|
`
|
package/lib/readAbstracts.js
CHANGED
|
@@ -38,7 +38,7 @@ function readAbstracts(abstracts, config, database, processors) {
|
|
|
38
38
|
) return recursiveProcess(rest, abstract, jobs)
|
|
39
39
|
|
|
40
40
|
const processed = processor.read.abstract(abstract, database, config)
|
|
41
|
-
processed.jobs && processed.jobs.forEach(job => job.
|
|
41
|
+
processed.jobs && processed.jobs.forEach(job => job.syntax = processor.syntax)
|
|
42
42
|
return recursiveProcess(rest, processed.abstract, [...jobs, ...(processed.jobs || [])])
|
|
43
43
|
}
|
|
44
44
|
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/** @import {VotiveConfig, FlatProcessors, Abstract, AbstractsWithSyntax, Jobs, FlatProcessor, Abstracts} from "./bundle.js" */
|
|
2
|
+
/** @import {Database} from "./createDatabase.js" */
|
|
3
|
+
/** @import {ReadSourceFileResult} from "./readSources.js"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {object} ReadAbstractsResult
|
|
7
|
+
* @property {Jobs} jobs
|
|
8
|
+
* @property {ReadAbstractResult[]} abstracts
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @typedef {object} ReadAbstractResult
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {ReadSourceFileResult} abstracts
|
|
17
|
+
* @param {VotiveConfig} config
|
|
18
|
+
* @param {Database} database
|
|
19
|
+
* @param {FlatProcessors} processors
|
|
20
|
+
* @returns {{processedAbstracts: Abstracts, abstractsJobs: Jobs}}
|
|
21
|
+
*/
|
|
22
|
+
function readBuffers(abstracts, config, database, processors) {
|
|
23
|
+
|
|
24
|
+
const processed = abstracts.flatMap(({ abstract: unprocessedAbstract, syntax }) => {
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @param {FlatProcessors} processors
|
|
28
|
+
* @param {Abstract} abstract
|
|
29
|
+
* @param {Jobs} jobs
|
|
30
|
+
*/
|
|
31
|
+
function recursiveProcess(processors, abstract, jobs = []) {
|
|
32
|
+
const [flatProcessor, ...rest] = processors
|
|
33
|
+
if (!flatProcessor) return { abstract, jobs }
|
|
34
|
+
const { processor, plugin } = flatProcessor
|
|
35
|
+
if (processor.syntax !== syntax
|
|
36
|
+
|| !processor.read
|
|
37
|
+
|| !processor.read.abstract
|
|
38
|
+
) return recursiveProcess(rest, abstract, jobs)
|
|
39
|
+
|
|
40
|
+
const processed = processor.read.abstract(abstract, database, config)
|
|
41
|
+
processed.jobs && processed.jobs.forEach(job => job.syntax = processor.syntax)
|
|
42
|
+
return recursiveProcess(rest, processed.abstract, [...jobs, ...(processed.jobs || [])])
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return recursiveProcess(processors, unprocessedAbstract)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
const processedAbstracts = []
|
|
49
|
+
const abstractsJobs = []
|
|
50
|
+
|
|
51
|
+
processed.forEach(({ abstract, jobs }) => {
|
|
52
|
+
processedAbstracts.push(abstract)
|
|
53
|
+
abstractsJobs.push(...jobs)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
return { processedAbstracts, abstractsJobs }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export default readAbstracts
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/** @import {VotiveConfig, FlatProcessors, Abstract, AbstractsWithSyntax, Jobs, FlatProcessor, Abstracts} from "./bundle.js" */
|
|
2
|
+
/** @import {Database} from "./createDatabase.js" */
|
|
3
|
+
/** @import {ReadSourceFileResult} from "./readSources.js"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {object} ReadAbstractsResult
|
|
7
|
+
* @property {Jobs} jobs
|
|
8
|
+
* @property {ReadAbstractResult[]} abstracts
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @typedef {object} ReadAbstractResult
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {ReadSourceFileResult} abstracts
|
|
17
|
+
* @param {VotiveConfig} config
|
|
18
|
+
* @param {Database} database
|
|
19
|
+
* @param {FlatProcessors} processors
|
|
20
|
+
* @returns {{processedAbstracts: Abstracts, abstractsJobs: Jobs}}
|
|
21
|
+
*/
|
|
22
|
+
function readFilePaths(abstracts, config, database, processors) {
|
|
23
|
+
|
|
24
|
+
const processed = abstracts.flatMap(({ abstract: unprocessedAbstract, syntax }) => {
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @param {FlatProcessors} processors
|
|
28
|
+
* @param {Abstract} abstract
|
|
29
|
+
* @param {Jobs} jobs
|
|
30
|
+
*/
|
|
31
|
+
function recursiveProcess(processors, abstract, jobs = []) {
|
|
32
|
+
const [flatProcessor, ...rest] = processors
|
|
33
|
+
if (!flatProcessor) return { abstract, jobs }
|
|
34
|
+
const { processor, plugin } = flatProcessor
|
|
35
|
+
if (processor.syntax !== syntax
|
|
36
|
+
|| !processor.read
|
|
37
|
+
|| !processor.read.abstract
|
|
38
|
+
) return recursiveProcess(rest, abstract, jobs)
|
|
39
|
+
|
|
40
|
+
const processed = processor.read.abstract(abstract, database, config)
|
|
41
|
+
processed.jobs && processed.jobs.forEach(job => job.syntax = processor.syntax)
|
|
42
|
+
return recursiveProcess(rest, processed.abstract, [...jobs, ...(processed.jobs || [])])
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return recursiveProcess(processors, unprocessedAbstract)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
const processedAbstracts = []
|
|
49
|
+
const abstractsJobs = []
|
|
50
|
+
|
|
51
|
+
processed.forEach(({ abstract, jobs }) => {
|
|
52
|
+
processedAbstracts.push(abstract)
|
|
53
|
+
abstractsJobs.push(...jobs)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
return { processedAbstracts, abstractsJobs }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export default readFilePaths
|
package/lib/readFolders.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import path from "node:path"
|
|
2
|
+
|
|
1
3
|
/** @import {VotiveConfig, FlatProcessors} from "./bundle.js" */
|
|
2
4
|
/** @import {Database} from "./createDatabase.js" */
|
|
3
5
|
/** @import {Dirent} from "node:fs" */
|
|
4
6
|
|
|
5
|
-
import path from "node:path"
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* @param {Dirent[]} folders
|
|
@@ -17,11 +18,13 @@ function readFolders(folders = [], config, database, processors) {
|
|
|
17
18
|
const processed = folderProcessors.flatMap(({ processor, plugin }) => {
|
|
18
19
|
|
|
19
20
|
const jobs = folders.flatMap(folder => {
|
|
20
|
-
|
|
21
|
+
let folderPath = path.join(folder.parentPath, folder.name)
|
|
22
|
+
if(folderPath) folderPath += path.sep
|
|
23
|
+
|
|
21
24
|
|
|
22
25
|
/** @ts-ignore `.read` is throwing a warning, but it's guarded above */
|
|
23
26
|
const { destinations, jobs } = processor.read.folder(folderPath, database, config)
|
|
24
|
-
jobs.forEach(job => job.
|
|
27
|
+
jobs.forEach(job => job.syntax = processor.syntax)
|
|
25
28
|
if (destinations) {
|
|
26
29
|
destinations.forEach(destination => {
|
|
27
30
|
database.createOrUpdateDestination(destination)
|
|
@@ -30,7 +33,14 @@ function readFolders(folders = [], config, database, processors) {
|
|
|
30
33
|
}
|
|
31
34
|
})
|
|
32
35
|
|
|
33
|
-
|
|
36
|
+
|
|
37
|
+
const rootInfo = path.parse(config.sourceFolder)
|
|
38
|
+
const rootPath = rootInfo.name
|
|
39
|
+
? path.format(rootInfo) + path.sep
|
|
40
|
+
: ""
|
|
41
|
+
|
|
42
|
+
// TODO: Add isRoot to type definition
|
|
43
|
+
const rootFolder = processor.read.folder(rootPath, database, config, true)
|
|
34
44
|
|
|
35
45
|
if (rootFolder.destinations) {
|
|
36
46
|
rootFolder.destinations.forEach(destination => {
|
|
@@ -38,7 +48,7 @@ function readFolders(folders = [], config, database, processors) {
|
|
|
38
48
|
})
|
|
39
49
|
}
|
|
40
50
|
|
|
41
|
-
rootFolder.jobs.forEach(job => job.
|
|
51
|
+
rootFolder.jobs.forEach(job => job.syntax = processor.syntax)
|
|
42
52
|
|
|
43
53
|
if(rootFolder.jobs) jobs.push(... rootFolder.jobs)
|
|
44
54
|
return jobs
|
package/lib/readSources.js
CHANGED
|
@@ -29,7 +29,7 @@ async function readSources(config, database, processors) {
|
|
|
29
29
|
const { files, folders } = Object.groupBy(filteredDirents, (dirent) => dirent.isFile() ? "files" : "folders")
|
|
30
30
|
if (!files) return { folders, sources: [] }
|
|
31
31
|
const readingSourceFiles = files.flatMap(readSourceFile(processors, database, config))
|
|
32
|
-
const sources = readingSourceFiles && await Promise.all(readingSourceFiles)
|
|
32
|
+
const sources = readingSourceFiles && (await Promise.all(readingSourceFiles)).filter(a => a)
|
|
33
33
|
return {
|
|
34
34
|
folders,
|
|
35
35
|
sources
|
|
@@ -43,6 +43,8 @@ async function readSources(config, database, processors) {
|
|
|
43
43
|
function fileFilter(config, database) {
|
|
44
44
|
/** @param {Dirent} dirent */
|
|
45
45
|
return (dirent) => {
|
|
46
|
+
const isDestinationFolder = !path.relative(config.destinationFolder, path.join(dirent.parentPath, dirent.name))
|
|
47
|
+
|
|
46
48
|
if (dirent.parentPath === config.destinationFolder) {
|
|
47
49
|
return false
|
|
48
50
|
} else if (dirent.parentPath.startsWith(config.destinationFolder + path.sep)) {
|
|
@@ -51,6 +53,10 @@ function fileFilter(config, database) {
|
|
|
51
53
|
return false // Ignore hidden files
|
|
52
54
|
} else if (dirent.parentPath.includes(path.sep + ".")) {
|
|
53
55
|
return false // Ignore hidden folders
|
|
56
|
+
} else if(dirent.parentPath.match(/^\.\w/)) {
|
|
57
|
+
return false // Ignore hidden folders
|
|
58
|
+
} if(isDestinationFolder) {
|
|
59
|
+
return false
|
|
54
60
|
}
|
|
55
61
|
return true
|
|
56
62
|
}
|
|
@@ -92,42 +98,85 @@ function readSourceFile(processors, database, config) {
|
|
|
92
98
|
*/
|
|
93
99
|
async function process(plugin, processor, config) {
|
|
94
100
|
const { read, filter, syntax } = processor
|
|
95
|
-
if (filter.extensions.includes(fileInfo.ext) && read && read.text) {
|
|
101
|
+
if (filter && filter.extensions.includes(fileInfo.ext) && read && (read.text || read.path)) {
|
|
96
102
|
|
|
97
103
|
// Check modified time
|
|
98
104
|
const stat = await fs.stat(filePath)
|
|
99
105
|
const source = database.getSource(filePath)
|
|
100
106
|
|
|
101
107
|
|
|
102
|
-
if (source && source.lastModified === Number(stat.mtimeMs.toFixed())) return
|
|
108
|
+
if (source && source.lastModified === Number(stat.mtimeMs.toFixed())) return null
|
|
103
109
|
|
|
104
110
|
database.deleteSettings(filePath)
|
|
105
111
|
|
|
106
112
|
// Get destination route
|
|
107
|
-
const destinationPath = route(filePath, plugin)
|
|
113
|
+
const destinationPath = route(filePath, plugin, config)
|
|
108
114
|
|
|
109
115
|
// Set URL if exists
|
|
110
|
-
const
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
116
|
+
const allJobs = []
|
|
117
|
+
const destination = {}
|
|
118
|
+
|
|
119
|
+
// TODO: Rename "path" to "asset"
|
|
120
|
+
if (read.path) {
|
|
121
|
+
const {metadata, abstract, jobs} = await read.path(filePath, database, config)
|
|
122
|
+
|
|
123
|
+
database.createOrUpdateDestination({
|
|
124
|
+
metadata,
|
|
125
|
+
abstract,
|
|
126
|
+
path: destinationPath,
|
|
127
|
+
syntax
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
if(Array.isArray(jobs)) {
|
|
131
|
+
allJobs.push(...jobs)
|
|
132
|
+
}
|
|
133
|
+
}
|
|
114
134
|
|
|
115
|
-
|
|
135
|
+
if (read.text) {
|
|
136
|
+
const buffer = await fs.readFile(path.format(fileInfo))
|
|
137
|
+
const data = decodeBuffer(buffer)
|
|
116
138
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
139
|
+
const { jobs, abstract, metadata } = read.text(data, filePath, destinationPath, database, config)
|
|
140
|
+
|
|
141
|
+
if(Array.isArray(jobs)) {
|
|
142
|
+
allJobs.push(...jobs)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
database.createOrUpdateDestination({
|
|
146
|
+
abstract,
|
|
147
|
+
path: destinationPath,
|
|
148
|
+
metadata,
|
|
149
|
+
syntax
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
// TODO: DRY up
|
|
153
|
+
allJobs && allJobs.forEach(job => job.syntax = processor.syntax)
|
|
154
|
+
updateSource()
|
|
155
|
+
return { abstract, destinationPath, syntax, jobs: allJobs }
|
|
121
156
|
}
|
|
122
157
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
158
|
+
// TODO: DRY up
|
|
159
|
+
allJobs && allJobs.forEach(job => job.syntax = processor.syntax)
|
|
160
|
+
updateSource()
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
jobs: allJobs,
|
|
164
|
+
abstract: null,
|
|
165
|
+
destinationPath: null,
|
|
166
|
+
metadata: null,
|
|
167
|
+
syntax
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function updateSource() {
|
|
171
|
+
const timeStamp = stat.mtimeMs.toFixed()
|
|
172
|
+
|
|
173
|
+
if (source) {
|
|
174
|
+
database.updateSource(filePath, Number(timeStamp))
|
|
175
|
+
} else {
|
|
176
|
+
database.createSource(filePath, destinationPath, Number(timeStamp))
|
|
177
|
+
}
|
|
178
|
+
}
|
|
129
179
|
|
|
130
|
-
return { abstract, destinationPath, syntax, jobs }
|
|
131
180
|
}
|
|
132
181
|
}
|
|
133
182
|
|
|
@@ -138,10 +187,22 @@ function readSourceFile(processors, database, config) {
|
|
|
138
187
|
/**
|
|
139
188
|
* @param {string} filePath
|
|
140
189
|
* @param {VotivePlugin} plugin
|
|
190
|
+
* @param {VotiveConfig} config
|
|
141
191
|
*/
|
|
142
|
-
function route(filePath, plugin) {
|
|
143
|
-
|
|
144
|
-
|
|
192
|
+
function route(filePath, plugin, config) {
|
|
193
|
+
const { dir, ...parsedPath } = path.parse(filePath)
|
|
194
|
+
const rooty = !path.relative(config.sourceFolder, dir)
|
|
195
|
+
const pathInfo = {
|
|
196
|
+
inRootDir: rooty,
|
|
197
|
+
dir: dir.split(path.sep),
|
|
198
|
+
...parsedPath
|
|
199
|
+
}
|
|
200
|
+
if (!plugin.router) return "0"
|
|
201
|
+
const routedPath = plugin.router(pathInfo)
|
|
202
|
+
if (routedPath && typeof routedPath.dir !== "string") {
|
|
203
|
+
routedPath.dir = path.join(...routedPath.dir)
|
|
204
|
+
}
|
|
205
|
+
return routedPath ? path.format(routedPath) : "0"
|
|
145
206
|
}
|
|
146
207
|
|
|
147
208
|
export default readSources
|
package/lib/runJobs.js
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
import workerpool from "workerpool"
|
|
2
|
-
|
|
3
1
|
/** @import {Job, Jobs, Database, VotiveConfig} from "./bundle.js" */
|
|
4
2
|
|
|
5
|
-
const pool = workerpool.pool()
|
|
6
3
|
|
|
7
4
|
/**
|
|
8
5
|
* @param {Jobs} jobs
|
|
@@ -10,14 +7,37 @@ const pool = workerpool.pool()
|
|
|
10
7
|
* @param {Database} database
|
|
11
8
|
*/
|
|
12
9
|
async function runJobs(jobs, config, database) {
|
|
13
|
-
const
|
|
10
|
+
const processors = config.plugins.flatMap(plugin => plugin.processors.flatMap(processor => processor.read?.url && ({ fetcher: processor.read.url, syntax: processor.syntax }))).filter(a => a)
|
|
11
|
+
const running = jobs.flatMap(async job => {
|
|
14
12
|
if (!job) return
|
|
15
|
-
const
|
|
16
|
-
|
|
13
|
+
const cachedURL = database.getURL(job.data)
|
|
14
|
+
if (cachedURL) return
|
|
15
|
+
const processing = processors.map(async processor => {
|
|
16
|
+
if (processor.syntax === job.syntax) {
|
|
17
|
+
try {
|
|
18
|
+
const response = await fetch(job.data)
|
|
19
|
+
// TODO: Change name of "job" to "url"
|
|
20
|
+
// TODO: Change name of "runner" to "format"
|
|
21
|
+
// TODO: Probably get rid of syntax filter
|
|
22
|
+
if (response.status >= 200 && response.status < 300) {
|
|
23
|
+
const data = await response[job.runner]()
|
|
24
|
+
const processed = processor.fetcher(data)
|
|
25
|
+
database.createURL(job.data, processed, job.destination)
|
|
26
|
+
return
|
|
27
|
+
} else {
|
|
28
|
+
console.warn(`Error fetching URL: ${job.data}`)
|
|
29
|
+
}
|
|
30
|
+
} catch (e) {
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
return Promise.allSettled(processing)
|
|
17
37
|
})
|
|
18
38
|
|
|
19
|
-
const
|
|
20
|
-
|
|
39
|
+
const settled = await Promise.allSettled(running)
|
|
40
|
+
return settled
|
|
21
41
|
}
|
|
22
42
|
|
|
23
43
|
export default runJobs
|
package/lib/sqlite.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// TODO: and, or, not, between, not in, order
|
|
2
|
+
|
|
3
|
+
const operators = {
|
|
4
|
+
glob: "GLOB",
|
|
5
|
+
like: "LIKE",
|
|
6
|
+
in: "IN",
|
|
7
|
+
gt: ">",
|
|
8
|
+
lt: "<",
|
|
9
|
+
gte: ">=",
|
|
10
|
+
lte: "<=",
|
|
11
|
+
equal: "=",
|
|
12
|
+
notEqual: "!-"
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const select = `SELECT * FROM destinations d`
|
|
16
|
+
const join = `LEFT JOIN metadata m ON d.path = m.destination`
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @typedef {Condition[]} Filter
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @typedef {object} Condition
|
|
24
|
+
* @property {string} property
|
|
25
|
+
* @property {keyof operators} operator
|
|
26
|
+
* @property {string | number | array} value
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @typedef {object} Query
|
|
31
|
+
* @property {number} [query.limit]
|
|
32
|
+
* @property {number} [query.offset]
|
|
33
|
+
* @property {string} [query.orderBy]
|
|
34
|
+
* @property {Filter} [query.filter]
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @param {Query} query
|
|
39
|
+
*/
|
|
40
|
+
export default function createStatement(query) {
|
|
41
|
+
const segments = [select, join]
|
|
42
|
+
if(query.limit) segments.push(`LIMIT ${query.limit}`)
|
|
43
|
+
if(query.offset) segments.push(`OFFSET ${query.offset}`)
|
|
44
|
+
if(query.orderBy) segments.push(`ORDER BY ${query.orderBy}`)
|
|
45
|
+
if(query.filter) {
|
|
46
|
+
const conditions = query.filter.map(condition => {
|
|
47
|
+
const { property, operator, value } = condition
|
|
48
|
+
|
|
49
|
+
if(["dir", "abstract", "path"].includes(property)) {
|
|
50
|
+
|
|
51
|
+
return `d.${property} ${operators[operator]} ${formatValue(value)}`
|
|
52
|
+
}
|
|
53
|
+
return `m.label = '${property}' AND m.value ${operators[operator]} ${formatValue(value)}`
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
segments.push(`WHERE ${conditions.join(" AND ")}`)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return segments.join(`\n`)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function formatProperty(property) {
|
|
63
|
+
if(property === "abstract") return `d.${property}`
|
|
64
|
+
else return `m.${property}`
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** @param {any} value */
|
|
68
|
+
function formatValue(value) {
|
|
69
|
+
if(Array.isArray(value)) {
|
|
70
|
+
return `(${value.map(x => `'${x}'`).join(', ')})`
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if(typeof value === "number") return value
|
|
74
|
+
|
|
75
|
+
return `'${value}'`
|
|
76
|
+
}
|
package/lib/utils/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { styleText } from "node:util"
|
|
2
2
|
import { statSync } from "node:fs"
|
|
3
|
+
import path from "path"
|
|
3
4
|
|
|
4
5
|
/** @param {string} label */
|
|
5
6
|
export function stopwatch(label) {
|
|
@@ -18,8 +19,11 @@ export function stopwatch(label) {
|
|
|
18
19
|
* @param {string} urlPath
|
|
19
20
|
*/
|
|
20
21
|
export function splitURL(urlPath) {
|
|
21
|
-
const [urlFileName, ...urlDirSegmentsReversed] = urlPath.split("/").reverse()
|
|
22
|
-
|
|
22
|
+
const [urlFileName, ...urlDirSegmentsReversed] = urlPath.split("/").filter(a => a).reverse()
|
|
23
|
+
const segments = urlDirSegmentsReversed.reverse()
|
|
24
|
+
segments.push('')
|
|
25
|
+
const folder = segments.join(path.sep)
|
|
26
|
+
return folder
|
|
23
27
|
}
|
|
24
28
|
|
|
25
29
|
/** @param {string} dbPath */
|
package/lib/writeDestinations.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { mkdir, writeFile } from "node:fs/promises"
|
|
2
2
|
import path from "node:path"
|
|
3
|
+
import { checkFile } from "./utils/index.js"
|
|
3
4
|
|
|
4
5
|
/** @import {Abstract, Abstracts, VotiveConfig} from "./bundle.js" */
|
|
5
6
|
/** @import {Database} from "./createDatabase.js" */
|
|
@@ -20,15 +21,29 @@ async function writeDestinations(config, database) {
|
|
|
20
21
|
if (!destinations) return
|
|
21
22
|
|
|
22
23
|
const writing = destinations.flatMap(destination => {
|
|
24
|
+
if(!destination.path) database.freshenDependency(destination.path)
|
|
23
25
|
const destinationPath = path.join(config.destinationFolder, String(destination.path))
|
|
24
26
|
const { dir } = path.parse(destinationPath)
|
|
25
|
-
return writeProcessors.map(processor => {
|
|
27
|
+
return writeProcessors.map(async processor => {
|
|
26
28
|
if (processor.syntax === destination.syntax) {
|
|
27
|
-
const
|
|
29
|
+
const writeInfo = await processor.write(destination, database, config)
|
|
30
|
+
if(!writeInfo) return
|
|
31
|
+
const { data, buffer, encoding = 'utf-8' } = writeInfo
|
|
28
32
|
|
|
29
33
|
async function write() {
|
|
30
|
-
|
|
31
|
-
|
|
34
|
+
const destinationExists = checkFile(dir)
|
|
35
|
+
|
|
36
|
+
// TODO: Avoid collisions
|
|
37
|
+
if(!destinationExists) {
|
|
38
|
+
await mkdir(dir, { recursive: true })
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if(buffer) {
|
|
42
|
+
await writeFile(destinationPath, buffer)
|
|
43
|
+
} else if(data) {
|
|
44
|
+
await writeFile(destinationPath, data, encoding)
|
|
45
|
+
}
|
|
46
|
+
|
|
32
47
|
database.freshenDependency(destination.path)
|
|
33
48
|
}
|
|
34
49
|
|
package/package.json
CHANGED
package/tests/.votive.db
CHANGED
|
Binary file
|
package/tests/index.js
CHANGED
|
@@ -56,7 +56,7 @@ test("internals", async (t) => {
|
|
|
56
56
|
|
|
57
57
|
|
|
58
58
|
/** @type {ReadText} */
|
|
59
|
-
function exampleTextReader(text, filePath, database, config) {
|
|
59
|
+
function exampleTextReader(text, filePath, destinationPath, database, config) {
|
|
60
60
|
const matches = text.match(/\b\w+\b/)
|
|
61
61
|
const title = matches ? matches[0] : "Untitled"
|
|
62
62
|
|
|
@@ -86,7 +86,7 @@ test("internals", async (t) => {
|
|
|
86
86
|
jobs: [createExampleJob()],
|
|
87
87
|
destinations: [
|
|
88
88
|
{
|
|
89
|
-
path: "
|
|
89
|
+
path: "index.html",
|
|
90
90
|
abstract: { content: "" },
|
|
91
91
|
metadata: {
|
|
92
92
|
title: "home"
|
|
@@ -116,9 +116,8 @@ test("internals", async (t) => {
|
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
/** @param {string} sourcePath */
|
|
119
|
-
function router(
|
|
120
|
-
|
|
121
|
-
return path.format({ ...parsed, ext: ".html" })
|
|
119
|
+
function router({ base, ...parsed }) {
|
|
120
|
+
return { ...parsed, ext: ".html" }
|
|
122
121
|
}
|
|
123
122
|
|
|
124
123
|
/** @type {VotivePlugin} */
|
|
@@ -207,7 +206,9 @@ test("internals", async (t) => {
|
|
|
207
206
|
})
|
|
208
207
|
|
|
209
208
|
database.createOrUpdateDestination({ metadata: { a: 3, b: 4 }, path: "abc/def.html", abstract: { c: 5 }, syntax: "md" })
|
|
210
|
-
const destination = database.getDestinationDependently("abc.html",
|
|
209
|
+
const destination = database.getDestinationDependently("abc.html", "abc/def.html")
|
|
210
|
+
|
|
211
|
+
console.info(`'abc/def.html' requests the property 'a' from 'abc.html', creating a dependency: ${destination.metadata.a}`)
|
|
211
212
|
|
|
212
213
|
const dependencies = database.getDependencies()
|
|
213
214
|
|
|
@@ -293,7 +294,9 @@ test("internals", async (t) => {
|
|
|
293
294
|
runJobs(jobs, config, database)
|
|
294
295
|
|
|
295
296
|
const destinations = database.getAllDestinations()
|
|
296
|
-
database.getDestinationDependently("markdown/prunee.html",
|
|
297
|
+
const prunee = database.getDestinationDependently("markdown/prunee.html", "abc/def.html")
|
|
298
|
+
|
|
299
|
+
console.info(`'abc/def.html' requests the property 'title' from 'markdown/prunee.html', creating a dependency: ${prunee.metadata.title}`)
|
|
297
300
|
const newDependencies = database.getDependencies()
|
|
298
301
|
|
|
299
302
|
fs.rmSync("markdown/prunee.md")
|
|
@@ -310,6 +313,20 @@ test("internals", async (t) => {
|
|
|
310
313
|
assert(!prunedMetadata)
|
|
311
314
|
})
|
|
312
315
|
|
|
316
|
+
const finalDestinations = database.getDestinations({
|
|
317
|
+
filter: [
|
|
318
|
+
{
|
|
319
|
+
property: "a",
|
|
320
|
+
operator: "gt",
|
|
321
|
+
value: 3
|
|
322
|
+
}
|
|
323
|
+
]
|
|
324
|
+
}, "markdown/prunee.html")
|
|
325
|
+
|
|
326
|
+
const finalDependencies = database.getDependencies()
|
|
327
|
+
|
|
328
|
+
// TODO: Write a test for get destinations
|
|
329
|
+
|
|
313
330
|
await database.saveDB()
|
|
314
331
|
|
|
315
332
|
temp.remove()
|