votive 0.0.3 → 0.0.5
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 +9 -1
- package/lib/bundle.js +52 -8
- package/lib/createDatabase.js +210 -85
- package/lib/index.js +1 -0
- package/lib/pruneSources.js +28 -0
- package/lib/readAbstracts.js +2 -2
- package/lib/readFolders.js +34 -12
- package/lib/readSources.js +11 -6
- package/lib/utils/index.js +10 -0
- package/lib/writeDestinations.js +1 -0
- package/package.json +3 -2
- package/tests/.votive.db +0 -0
- package/tests/index.js +223 -58
- package/tests/markdown/fldr/dog.md +3 -0
- package/tests/markdown/hey.md +3 -0
- package/tests/markdown/index.md +1 -0
- package/tests/markdown/source/fldr/dog.md +0 -0
- package/tests/markdown/source/hey.md +0 -0
- package/tests/markdown/source/index.md +0 -0
package/README.md
CHANGED
package/lib/bundle.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import
|
|
2
|
-
import readSources from "./readSources.js"
|
|
1
|
+
import path from "node:path"
|
|
3
2
|
import readAbstracts from "./readAbstracts.js"
|
|
4
3
|
import readFolders from "./readFolders.js"
|
|
5
|
-
import
|
|
4
|
+
import readSources from "./readSources.js"
|
|
6
5
|
import runJobs from "./runJobs.js"
|
|
6
|
+
import writeDestinations from "./writeDestinations.js"
|
|
7
|
+
import { default as createDatabase } from "./createDatabase.js"
|
|
7
8
|
|
|
8
9
|
/** @import {Database} from "./createDatabase.js" */
|
|
9
10
|
|
|
@@ -54,6 +55,7 @@ import runJobs from "./runJobs.js"
|
|
|
54
55
|
/**
|
|
55
56
|
* @callback ReadText
|
|
56
57
|
* @param {string} text
|
|
58
|
+
* @param {string} filePath
|
|
57
59
|
* @param {Database} database
|
|
58
60
|
* @param {VotiveConfig} config
|
|
59
61
|
* @returns {ReadTextResult | undefined}
|
|
@@ -101,6 +103,15 @@ import runJobs from "./runJobs.js"
|
|
|
101
103
|
* @param {object} Folder
|
|
102
104
|
* @param {Database} database
|
|
103
105
|
* @param {VotiveConfig} config
|
|
106
|
+
* @returns {{ jobs?: Jobs, destinations?: Destination[] }}
|
|
107
|
+
*/
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* @typedef {object} Destination
|
|
111
|
+
* @property {string} path
|
|
112
|
+
* @property {object} metadata
|
|
113
|
+
* @property {Abstract} abstract
|
|
114
|
+
* @property {string} syntax
|
|
104
115
|
*/
|
|
105
116
|
|
|
106
117
|
/**
|
|
@@ -158,21 +169,54 @@ import runJobs from "./runJobs.js"
|
|
|
158
169
|
|
|
159
170
|
/**
|
|
160
171
|
* @param {VotiveConfig} config
|
|
172
|
+
* @param {Database | undefined} [cache]
|
|
161
173
|
*/
|
|
162
|
-
async function bundle(config) {
|
|
174
|
+
async function bundle(config, cache) {
|
|
175
|
+
|
|
176
|
+
// Map out all processors
|
|
163
177
|
const processors = config.plugins
|
|
164
178
|
&& config.plugins.flatMap(plugin => plugin.processors && plugin.processors.map(processor => ({ plugin, processor })))
|
|
165
179
|
|
|
166
|
-
|
|
180
|
+
// Create database
|
|
181
|
+
const database = cache || createDatabase(path.join(config.sourceFolder, ".votive.db"))
|
|
182
|
+
|
|
183
|
+
/*
|
|
184
|
+
Note: If no cache is provided or located, the database
|
|
185
|
+
will automatically run in memory, based on the assumption
|
|
186
|
+
that Votive is running for the first time. The in-
|
|
187
|
+
memory database will run much faster and then back up
|
|
188
|
+
to the file system. When Votive next launches from the
|
|
189
|
+
cached disk database, the read/writes will be a little
|
|
190
|
+
slower, but startup will be much faster, so it should
|
|
191
|
+
even out.
|
|
192
|
+
*/
|
|
193
|
+
|
|
194
|
+
// Read folders and source files
|
|
167
195
|
const { folders, sources } = await readSources(config, database, processors)
|
|
168
196
|
|
|
197
|
+
// Map out jobs from source files
|
|
169
198
|
const sourcesJobs = sources.flatMap(source => source.jobs) || []
|
|
170
|
-
|
|
199
|
+
|
|
200
|
+
// Process source file abstracts and map jobs
|
|
201
|
+
const { abstractsJobs } = readAbstracts(sources, config, database, processors)
|
|
202
|
+
|
|
203
|
+
// Scan folders and map out jobs
|
|
171
204
|
const foldersJobs = readFolders(folders, config, database, processors) || []
|
|
205
|
+
|
|
206
|
+
// Write destination files
|
|
172
207
|
await writeDestinations(config, database)
|
|
173
208
|
|
|
174
|
-
|
|
175
|
-
await runJobs(
|
|
209
|
+
// Run all jobs
|
|
210
|
+
await runJobs([
|
|
211
|
+
...sourcesJobs,
|
|
212
|
+
...abstractsJobs,
|
|
213
|
+
...foldersJobs
|
|
214
|
+
], config, database)
|
|
215
|
+
|
|
216
|
+
// Back up database (only if in-memory first run)
|
|
217
|
+
await database.saveDB()
|
|
218
|
+
|
|
219
|
+
return database
|
|
176
220
|
}
|
|
177
221
|
|
|
178
222
|
export default bundle
|
package/lib/createDatabase.js
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
import { DatabaseSync, backup } from "node:sqlite"
|
|
2
|
-
import { splitURL } from "./utils/index.js"
|
|
2
|
+
import { splitURL, checkFile } from "./utils/index.js"
|
|
3
3
|
import { statSync } from "node:fs"
|
|
4
|
+
import path from "node:path"
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* @typedef {ReturnType<createDatabase>} Database
|
|
7
8
|
*/
|
|
8
9
|
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
return null
|
|
15
|
-
}
|
|
11
|
+
/** @param {string} dbPath */
|
|
12
|
+
function loadDB(dbPath) {
|
|
13
|
+
if (checkFile(dbPath)) return new DatabaseSync(dbPath)
|
|
14
|
+
return new DatabaseSync(":memory:")
|
|
16
15
|
}
|
|
17
16
|
|
|
18
17
|
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
/** @param {string} dbPath */
|
|
19
|
+
function createDatabase(dbPath = ".votive.db") {
|
|
20
|
+
const databaseSync = loadDB(dbPath)
|
|
21
21
|
|
|
22
22
|
const store = databaseSync.createTagStore()
|
|
23
23
|
|
|
@@ -25,6 +25,11 @@ function createDatabase() {
|
|
|
25
25
|
|
|
26
26
|
const database = {}
|
|
27
27
|
|
|
28
|
+
database.saveDB = async () => {
|
|
29
|
+
if (databaseSync.location()) return // Only save if running in memory
|
|
30
|
+
await backup(databaseSync, dbPath)
|
|
31
|
+
}
|
|
32
|
+
|
|
28
33
|
/**
|
|
29
34
|
* @param {string} folderPath
|
|
30
35
|
* @param {string} urlPath
|
|
@@ -42,9 +47,21 @@ function createDatabase() {
|
|
|
42
47
|
*/
|
|
43
48
|
database.createSource = (source, destination, lastModified) => {
|
|
44
49
|
databaseSync.prepare(``) // SQLite bug. Query fails without this.
|
|
45
|
-
|
|
50
|
+
createSource.get(source, destination, lastModified)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const updateSource = databaseSync.prepare(`UPDATE sources SET lastModified = ? WHERE path = ?`)
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @param {string} source
|
|
57
|
+
* @param {number} lastModified
|
|
58
|
+
*/
|
|
59
|
+
database.updateSource = (source, lastModified) => {
|
|
60
|
+
updateSource.get(lastModified, source)
|
|
46
61
|
}
|
|
47
62
|
|
|
63
|
+
const getSettingsBySource = databaseSync.prepare(`SELECT * FROM settings WHERE source = ?`)
|
|
64
|
+
|
|
48
65
|
|
|
49
66
|
// These statements are cached, no need to refactor
|
|
50
67
|
const createDest = databaseSync.prepare(`INSERT INTO destinations (path, dir, syntax, stale, abstract) VALUES (?, ?, ?, 1, ?)`)
|
|
@@ -52,8 +69,25 @@ function createDatabase() {
|
|
|
52
69
|
const createMeta = databaseSync.prepare(`INSERT INTO metadata (destination, label, value, type) VALUES (?, ?, ?, ?)`)
|
|
53
70
|
const updateMeta = databaseSync.prepare(`UPDATE metadata SET value = ? WHERE destination = ? AND label = ?`)
|
|
54
71
|
const getDepends = databaseSync.prepare(`SELECT * FROM dependencies WHERE destination = ? AND property = ?`)
|
|
72
|
+
const getDependenciesByDestination = databaseSync.prepare(`SELECT * FROM dependencies WHERE destination = ?`)
|
|
73
|
+
const getAllDeps = databaseSync.prepare(`SELECT * FROM dependencies`)
|
|
55
74
|
const staleDepen = databaseSync.prepare(`UPDATE destinations SET stale = 1 WHERE path = ?`)
|
|
75
|
+
const freshDepen = databaseSync.prepare(`UPDATE destinations SET stale = 0 WHERE path = ? RETURNING *`)
|
|
56
76
|
const staleDescendents = databaseSync.prepare(`UPDATE destinations SET stale = 1 WHERE path LIKE ? RETURNING *`)
|
|
77
|
+
const getAllSettings = databaseSync.prepare(`SELECT * FROM settings`)
|
|
78
|
+
|
|
79
|
+
database.getAllSettings = () => {
|
|
80
|
+
return getAllSettings.all()
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** @param {string} path */
|
|
84
|
+
database.freshenDependency = (path) => {
|
|
85
|
+
freshDepen.get(path)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
database.getDependencies = () => {
|
|
89
|
+
return getAllDeps.all()
|
|
90
|
+
}
|
|
57
91
|
|
|
58
92
|
/**
|
|
59
93
|
* @param {object} params
|
|
@@ -75,10 +109,18 @@ function createDatabase() {
|
|
|
75
109
|
}
|
|
76
110
|
|
|
77
111
|
const changedAbstract = JSON.stringify(dest.abstract) !== JSON.stringify(extant.abstract)
|
|
78
|
-
|
|
112
|
+
|
|
113
|
+
let changedMetadata = false
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
// TODO: This is redundant with `getDestinationIndependently`
|
|
117
|
+
const cachedMetadata = getMetadataByPath.all(dest.path)
|
|
79
118
|
|
|
80
119
|
for (const key in metadata) {
|
|
120
|
+
const index = cachedMetadata.findIndex(el => el.label === key)
|
|
121
|
+
cachedMetadata.splice(index, 1)
|
|
81
122
|
if (JSON.stringify(metadata[key]) !== JSON.stringify(extant.metadata[key])) {
|
|
123
|
+
changedMetadata = true
|
|
82
124
|
updateMeta.get(metadata[key], dest.path, key)
|
|
83
125
|
const dependencies = getDepends.all(dest.path, key)
|
|
84
126
|
dependencies.forEach(({ dependent }) => {
|
|
@@ -87,8 +129,19 @@ function createDatabase() {
|
|
|
87
129
|
}
|
|
88
130
|
}
|
|
89
131
|
|
|
132
|
+
cachedMetadata.forEach(deletedDatum => {
|
|
133
|
+
changedMetadata = true
|
|
134
|
+
const dependencies = getDepends.all(dest.path, deletedDatum.label)
|
|
135
|
+
dependencies.forEach(({ dependent }) => {
|
|
136
|
+
staleDepen.get(dependent)
|
|
137
|
+
})
|
|
138
|
+
deleteMetadata.get(deletedDatum.destination, deletedDatum.label)
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
if (changedAbstract || changedMetadata) staleDepen.get(dest.path)
|
|
142
|
+
|
|
90
143
|
if (changedAbstract) {
|
|
91
|
-
|
|
144
|
+
updateDest.get(JSON.stringify(dest.abstract), dest.path)
|
|
92
145
|
const dependencies = getDepends.all(dest.path, "abstract")
|
|
93
146
|
dependencies.forEach(({ dependent }) => {
|
|
94
147
|
staleDepen.get(dependent)
|
|
@@ -96,17 +149,43 @@ function createDatabase() {
|
|
|
96
149
|
}
|
|
97
150
|
}
|
|
98
151
|
|
|
152
|
+
const getMetadataByPath = databaseSync.prepare(`
|
|
153
|
+
SELECT * FROM metadata WHERE destination = ?
|
|
154
|
+
`)
|
|
155
|
+
|
|
156
|
+
/** @param {string} path */
|
|
157
|
+
database.getAllMetadataByPath = (path) => {
|
|
158
|
+
return getMetadataByPath.get(path)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** @param {string[]} params */
|
|
162
|
+
database.getMetadataIndependently = (params) => {
|
|
163
|
+
return databaseSync.prepare(`
|
|
164
|
+
SELECT * FROM destinations d
|
|
165
|
+
LEFT JOIN metadata m ON d.path = m.destination AND m.label IN (${params.map(p => `'${p}'`).join(", ")})
|
|
166
|
+
WHERE d.path = ?
|
|
167
|
+
`)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
database.getDestinationWithoutMetadata = () => {
|
|
171
|
+
return databaseSync.prepare(`
|
|
172
|
+
SELECT * FROM destinations
|
|
173
|
+
WHERE path = ?
|
|
174
|
+
`)
|
|
175
|
+
}
|
|
176
|
+
|
|
99
177
|
/**
|
|
100
178
|
* @param {string} path
|
|
101
|
-
* @param {string[]} params
|
|
179
|
+
* @param {string[]} [params]
|
|
102
180
|
*/
|
|
103
181
|
database.getDestinationIndependently = (path, params) => {
|
|
104
182
|
// TODO: Could potentially speed up the next two db queries by using the tag store
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
183
|
+
|
|
184
|
+
const metadatas = (params && params.length)
|
|
185
|
+
? database.getMetadataIndependently(params).all(path)
|
|
186
|
+
: [database.getDestinationWithoutMetadata().get(path)]
|
|
187
|
+
|
|
188
|
+
if (!metadatas) return
|
|
110
189
|
|
|
111
190
|
const [first] = metadatas
|
|
112
191
|
|
|
@@ -123,7 +202,7 @@ function createDatabase() {
|
|
|
123
202
|
: typeof value !== "string"
|
|
124
203
|
? value
|
|
125
204
|
: JSON.parse(value)
|
|
126
|
-
return [label,
|
|
205
|
+
return [label, originalValue]
|
|
127
206
|
})
|
|
128
207
|
)
|
|
129
208
|
|
|
@@ -135,7 +214,7 @@ function createDatabase() {
|
|
|
135
214
|
metadata
|
|
136
215
|
}
|
|
137
216
|
|
|
138
|
-
const includeAbstract = params.includes("abstract")
|
|
217
|
+
const includeAbstract = params && params.includes("abstract")
|
|
139
218
|
if (includeAbstract) result.abstract = JSON.parse(first.abstract)
|
|
140
219
|
|
|
141
220
|
return result
|
|
@@ -157,7 +236,7 @@ function createDatabase() {
|
|
|
157
236
|
|
|
158
237
|
const result = database.getDestinationIndependently(path, params)
|
|
159
238
|
if (result.abstract) deps.push(`('${path}, 'abstract', '${dependent}')`)
|
|
160
|
-
|
|
239
|
+
databaseSync.prepare(`INSERT INTO dependencies (destination, property, dependent) VALUES ${deps.join(", ")} RETURNING *`).all()
|
|
161
240
|
}
|
|
162
241
|
|
|
163
242
|
const selectSource = databaseSync.prepare(`SELECT * FROM sources WHERE path = ?`)
|
|
@@ -175,45 +254,138 @@ function createDatabase() {
|
|
|
175
254
|
return sources
|
|
176
255
|
}
|
|
177
256
|
|
|
257
|
+
const deleteSource = databaseSync.prepare(`DELETE FROM sources WHERE path = ? RETURNING *`)
|
|
258
|
+
const deleteAllDependenciesByDestination = databaseSync.prepare(`DELETE FROM dependencies WHERE destination = ? RETURNING dependent`)
|
|
259
|
+
const deleteAllMetadataByDestination = databaseSync.prepare(`DELETE FROM metadata WHERE destination = ?`)
|
|
260
|
+
const deleteMetadata = databaseSync.prepare(`DELETE FROM metadata WHERE destination = ? AND label = ?`)
|
|
261
|
+
const deleteDestination = databaseSync.prepare(`DELETE FROM destinations WHERE path = ?`)
|
|
262
|
+
|
|
263
|
+
/** @param {string} path */
|
|
264
|
+
database.deleteSource = (path) => {
|
|
265
|
+
const deleted = deleteSource.all(path)
|
|
266
|
+
deleted.forEach(source => {
|
|
267
|
+
database.deleteSettings(String(source.path))
|
|
268
|
+
getDependenciesByDestination.all(source.destination)
|
|
269
|
+
deleteAllDependenciesByDestination.all(source.destination)
|
|
270
|
+
.forEach(({ dependent }) => staleDepen.get(dependent))
|
|
271
|
+
deleteAllMetadataByDestination.all(source.destination)
|
|
272
|
+
deleteDestination.get(source.destination)
|
|
273
|
+
})
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// TODO: DELETE SETTING
|
|
277
|
+
|
|
178
278
|
database.getAllDestinations = () => {
|
|
179
279
|
return store.all`SELECT * FROM destinations`
|
|
180
280
|
}
|
|
181
281
|
|
|
182
282
|
database.getStaleDestinations = () => {
|
|
183
|
-
|
|
283
|
+
const metadatas = store.all`
|
|
284
|
+
SELECT * FROM destinations d
|
|
285
|
+
LEFT JOIN metadata m on d.path = m.destination
|
|
286
|
+
WHERE stale = 1`
|
|
287
|
+
|
|
288
|
+
console.log({ metadatas })
|
|
289
|
+
|
|
290
|
+
const grouped = metadatas.map((value, index, array) => {
|
|
291
|
+
if (array.slice(0, index)
|
|
292
|
+
.find(prior => prior.path === value.path)
|
|
293
|
+
) {
|
|
294
|
+
return null
|
|
295
|
+
} else {
|
|
296
|
+
return {
|
|
297
|
+
path: value.path,
|
|
298
|
+
dir: value.dir,
|
|
299
|
+
syntax: value.syntax,
|
|
300
|
+
stale: value.stale,
|
|
301
|
+
metadata: Object.fromEntries(
|
|
302
|
+
array.slice(index)
|
|
303
|
+
.filter(following => following.path === value.path)
|
|
304
|
+
.map(succeeding => {
|
|
305
|
+
return reconstituteMetadata(succeeding)
|
|
306
|
+
})
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}).filter(a => a)
|
|
312
|
+
|
|
313
|
+
function reconstituteMetadata({ label, value, type }) {
|
|
314
|
+
const originalValue = type === "undefined"
|
|
315
|
+
? undefined
|
|
316
|
+
: type === "string"
|
|
317
|
+
? String(value)
|
|
318
|
+
: type === "boolean"
|
|
319
|
+
? Boolean(value)
|
|
320
|
+
: typeof value !== "string"
|
|
321
|
+
? value
|
|
322
|
+
: JSON.parse(value)
|
|
323
|
+
return [label, originalValue]
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return grouped
|
|
184
327
|
}
|
|
185
328
|
|
|
186
|
-
const createSetting = databaseSync.prepare(`INSERT INTO settings (destination, label, value) VALUES (?, ?, ?) RETURNING *`)
|
|
329
|
+
const createSetting = databaseSync.prepare(`INSERT INTO settings (destination, label, value, source) VALUES (?, ?, ?, ?) RETURNING *`)
|
|
187
330
|
const selectSetting = databaseSync.prepare(`SELECT * FROM settings WHERE label = ? AND destination = ?`)
|
|
188
|
-
const updateSetting = databaseSync.prepare(`UPDATE settings SET value = ? WHERE destination = ? AND label =
|
|
331
|
+
const updateSetting = databaseSync.prepare(`UPDATE settings SET value = ? WHERE destination = ? AND label = ? RETURNING *`)
|
|
332
|
+
const deleteSettings = databaseSync.prepare(`DELETE FROM settings WHERE source = ?`)
|
|
189
333
|
|
|
190
334
|
// const updateDest = databaseSync.prepare(`UPDATE destinations SET abstract = ? WHERE path = ?`)
|
|
191
335
|
|
|
336
|
+
// WRITE AN UPDATE SETTINGS FUNCTION FOR WHEN A SOURCE CHANGES
|
|
337
|
+
|
|
338
|
+
/** @param {string} source */
|
|
339
|
+
database.deleteSettings = (source) => {
|
|
340
|
+
const deleted = deleteSettings.all(source)
|
|
341
|
+
deleted.forEach(setting => {
|
|
342
|
+
staleDepen.get(setting.destination)
|
|
343
|
+
staleDescendents.all(`${setting.destination}/%`)
|
|
344
|
+
})
|
|
345
|
+
}
|
|
346
|
+
|
|
192
347
|
/**
|
|
193
|
-
* @param {string}
|
|
348
|
+
* @param {string} destinationFolder
|
|
194
349
|
* @param {string} key
|
|
195
350
|
* @param {string} value
|
|
351
|
+
* @param {string} [source]
|
|
352
|
+
* @example
|
|
353
|
+
* setSetting("", "theme", "summer", "settings.md")
|
|
354
|
+
* setSetting("data", "format", "csv", "/config.yaml")
|
|
196
355
|
*/
|
|
197
|
-
database.setSetting = (
|
|
198
|
-
|
|
199
|
-
const extant = selectSetting.get(key, destination)
|
|
356
|
+
database.setSetting = (destinationFolder, key, value, source = "") => {
|
|
357
|
+
const extant = selectSetting.get(key, destinationFolder)
|
|
200
358
|
|
|
201
|
-
if (!extant) return createSetting.get(
|
|
359
|
+
if (!extant) return createSetting.get(destinationFolder, key, value, source)
|
|
202
360
|
if (extant.value === value) return
|
|
361
|
+
updateSetting.get(value, destinationFolder, key)
|
|
203
362
|
|
|
204
|
-
|
|
205
|
-
|
|
363
|
+
destinationFolder === ""
|
|
364
|
+
? staleDescendents.all("%")
|
|
365
|
+
: staleDescendents.all(`${destinationFolder}/%`)
|
|
206
366
|
}
|
|
207
367
|
|
|
208
368
|
|
|
209
369
|
/**
|
|
210
|
-
* @param {string}
|
|
211
|
-
* @param {string} dependent
|
|
370
|
+
* @param {string} destinationFolder
|
|
212
371
|
*/
|
|
213
|
-
database.getSettings = (
|
|
214
|
-
|
|
215
|
-
.
|
|
216
|
-
|
|
372
|
+
database.getSettings = (destinationFolder) => {
|
|
373
|
+
const segments = destinationFolder.split(path.sep)
|
|
374
|
+
.map((_, index, array) => `'${array.slice(0, index + 1).join(path.sep)}'`)
|
|
375
|
+
|
|
376
|
+
segments.unshift("''")
|
|
377
|
+
const statement = segments.join(", ")
|
|
378
|
+
|
|
379
|
+
const records = databaseSync.prepare(`SELECT * FROM settings WHERE destination IN (${statement})`).all()
|
|
380
|
+
|
|
381
|
+
const grouped = {}
|
|
382
|
+
records.sort((a, b) => a.length - b.length)
|
|
383
|
+
.forEach(record => {
|
|
384
|
+
if (!grouped[record.label]) grouped[record.label] = [record.value]
|
|
385
|
+
else grouped[record.label].push(record.value)
|
|
386
|
+
})
|
|
387
|
+
|
|
388
|
+
return grouped
|
|
217
389
|
}
|
|
218
390
|
|
|
219
391
|
database.getEverything = () => {
|
|
@@ -230,50 +402,7 @@ function createDatabase() {
|
|
|
230
402
|
return database
|
|
231
403
|
}
|
|
232
404
|
|
|
233
|
-
/**
|
|
234
|
-
* @param {object} data
|
|
235
|
-
* @param {string} table
|
|
236
|
-
* @param {DatabaseSync} databaseSync
|
|
237
|
-
*/
|
|
238
|
-
function updateColumns(data, table, databaseSync) {
|
|
239
|
-
const keys = new Set(Object.keys(data))
|
|
240
|
-
const columns = listColumns(table, databaseSync)
|
|
241
|
-
const newColumns = keys.difference(columns)
|
|
242
|
-
for (const column of newColumns) addColumn(column, table, databaseSync)
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
/**
|
|
246
|
-
* @param {string} table
|
|
247
|
-
* @param {DatabaseSync} databaseSync
|
|
248
|
-
* @description list the columns on a table.
|
|
249
|
-
*/
|
|
250
|
-
function listColumns(table, databaseSync) {
|
|
251
|
-
const columns = new Set()
|
|
252
|
-
const tableInfo = databaseSync.prepare(`PRAGMA table_info(${table})`).all()
|
|
253
|
-
for (const column of tableInfo) columns.add(column.name)
|
|
254
|
-
return columns
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* @param {string} column
|
|
259
|
-
* @param {string} table
|
|
260
|
-
* @param {DatabaseSync} databaseSync
|
|
261
|
-
* @description Add a column to a table and update binary.
|
|
262
|
-
*/
|
|
263
|
-
function addColumn(column, table, databaseSync) {
|
|
264
|
-
databaseSync.exec(`ALTER TABLE ${table} ADD COLUMN ${column} STRING;`)
|
|
265
|
-
}
|
|
266
|
-
|
|
267
405
|
const sqlCreateTables = `
|
|
268
|
-
CREATE TABLE IF NOT EXISTS jobs (
|
|
269
|
-
id INTEGER PRIMARY KEY,
|
|
270
|
-
pluginName TEXT NOT NULL,
|
|
271
|
-
data STRING NOT NULL,
|
|
272
|
-
done INTEGER NOT NULL DEFAULT 0,
|
|
273
|
-
runner STRING NOT NULL
|
|
274
|
-
);
|
|
275
|
-
|
|
276
|
-
-- Source/destination relationships
|
|
277
406
|
CREATE TABLE IF NOT EXISTS sources (
|
|
278
407
|
id INTEGER PRIMARY KEY,
|
|
279
408
|
destination STRING,
|
|
@@ -297,11 +426,6 @@ CREATE TABLE IF NOT EXISTS destinations (
|
|
|
297
426
|
abstract
|
|
298
427
|
);
|
|
299
428
|
|
|
300
|
-
CREATE TABLE IF NOT EXISTS folders (
|
|
301
|
-
path STRING PRIMARY KEY,
|
|
302
|
-
urlPath STRING
|
|
303
|
-
);
|
|
304
|
-
|
|
305
429
|
CREATE TABLE IF NOT EXISTS metadata (
|
|
306
430
|
id INTEGER PRIMARY KEY,
|
|
307
431
|
destination STRING,
|
|
@@ -314,6 +438,7 @@ CREATE TABLE IF NOT EXISTS metadata (
|
|
|
314
438
|
CREATE TABLE IF NOT EXISTS settings (
|
|
315
439
|
id INTEGER PRIMARY KEY,
|
|
316
440
|
destination STRING,
|
|
441
|
+
source STRING,
|
|
317
442
|
label STRING,
|
|
318
443
|
value STRING
|
|
319
444
|
);
|
package/lib/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { default as bundle } from "./bundle.js"
|
|
2
2
|
export { default as createDatabase } from "./createDatabase.js"
|
|
3
|
+
export { default as pruneSources } from "./pruneSources.js"
|
|
3
4
|
export { default as readAbstracts } from "./readAbstracts.js"
|
|
4
5
|
export { default as readFolders } from "./readFolders.js"
|
|
5
6
|
export { default as readSources } from "./readSources.js"
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { readdir } from "node:fs/promises"
|
|
2
|
+
import path from "node:path"
|
|
3
|
+
/** @import {VotiveConfig} from "./bundle.js" */
|
|
4
|
+
/** @import {Database} from "./createDatabase.js" */
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @param {VotiveConfig} config
|
|
10
|
+
* @param {Database} database
|
|
11
|
+
*/
|
|
12
|
+
async function pruneSources(config, database) {
|
|
13
|
+
const sources = database.getAllSources()
|
|
14
|
+
const files = (await readdir(config.sourceFolder, {
|
|
15
|
+
withFileTypes: true,
|
|
16
|
+
recursive: true
|
|
17
|
+
})).map(file => {
|
|
18
|
+
return file.isFile() ? path.join(file.parentPath, file.name) : null
|
|
19
|
+
}).filter(a => typeof a === "string")
|
|
20
|
+
|
|
21
|
+
sources.forEach(source => {
|
|
22
|
+
if(files.includes(String(source.path))) return
|
|
23
|
+
database.deleteSource(String(source.path))
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default pruneSources
|
package/lib/readAbstracts.js
CHANGED
|
@@ -48,9 +48,9 @@ function readAbstracts(abstracts, config, database, processors) {
|
|
|
48
48
|
const processedAbstracts = []
|
|
49
49
|
const abstractsJobs = []
|
|
50
50
|
|
|
51
|
-
processed.forEach(({ abstract,
|
|
51
|
+
processed.forEach(({ abstract, jobs }) => {
|
|
52
52
|
processedAbstracts.push(abstract)
|
|
53
|
-
abstractsJobs.push(
|
|
53
|
+
abstractsJobs.push(...jobs)
|
|
54
54
|
})
|
|
55
55
|
|
|
56
56
|
return { processedAbstracts, abstractsJobs }
|
package/lib/readFolders.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** @import {VotiveConfig, FlatProcessors
|
|
1
|
+
/** @import {VotiveConfig, FlatProcessors} from "./bundle.js" */
|
|
2
2
|
/** @import {Database} from "./createDatabase.js" */
|
|
3
3
|
/** @import {Dirent} from "node:fs" */
|
|
4
4
|
|
|
@@ -9,20 +9,42 @@ import path from "node:path"
|
|
|
9
9
|
* @param {VotiveConfig} config
|
|
10
10
|
* @param {Database} database
|
|
11
11
|
* @param {FlatProcessors} processors
|
|
12
|
-
* @returns {Jobs}
|
|
13
12
|
*/
|
|
14
|
-
function readFolders(folders, config, database, processors) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
13
|
+
function readFolders(folders = [], config, database, processors) {
|
|
14
|
+
|
|
15
|
+
const folderProcessors = processors.filter(({ processor }) => processor.read && processor.read.folder)
|
|
16
|
+
|
|
17
|
+
const processed = folderProcessors.flatMap(({ processor, plugin }) => {
|
|
18
|
+
|
|
19
|
+
const jobs = folders.flatMap(folder => {
|
|
20
|
+
const folderPath = path.relative(config.sourceFolder, path.join(folder.parentPath, folder.name))
|
|
21
|
+
|
|
22
|
+
/** @ts-ignore `.read` is throwing a warning, but it's guarded above */
|
|
23
|
+
const { destinations, jobs } = processor.read.folder(folderPath, database, config)
|
|
24
|
+
jobs.forEach(job => job.plugin = plugin.name)
|
|
25
|
+
if (destinations) {
|
|
26
|
+
destinations.forEach(destination => {
|
|
27
|
+
database.createOrUpdateDestination(destination)
|
|
28
|
+
})
|
|
29
|
+
return jobs
|
|
30
|
+
}
|
|
24
31
|
})
|
|
32
|
+
|
|
33
|
+
const rootFolder = processor.read.folder("", database, config)
|
|
34
|
+
|
|
35
|
+
if (rootFolder.destinations) {
|
|
36
|
+
rootFolder.destinations.forEach(destination => {
|
|
37
|
+
database.createOrUpdateDestination(destination)
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
rootFolder.jobs.forEach(job => job.plugin = plugin.name)
|
|
42
|
+
|
|
43
|
+
if(rootFolder.jobs) jobs.push(... rootFolder.jobs)
|
|
44
|
+
return jobs
|
|
25
45
|
})
|
|
46
|
+
|
|
47
|
+
return processed
|
|
26
48
|
}
|
|
27
49
|
|
|
28
50
|
export default readFolders
|
package/lib/readSources.js
CHANGED
|
@@ -25,8 +25,6 @@ async function readSources(config, database, processors) {
|
|
|
25
25
|
recursive: true
|
|
26
26
|
})
|
|
27
27
|
|
|
28
|
-
// TODO: Check file modified time
|
|
29
|
-
|
|
30
28
|
const filteredDirents = (dirents || []).filter(fileFilter(config, database))
|
|
31
29
|
const { files, folders } = Object.groupBy(filteredDirents, (dirent) => dirent.isFile() ? "files" : "folders")
|
|
32
30
|
if (!files) return { folders, sources: [] }
|
|
@@ -101,7 +99,9 @@ function readSourceFile(processors, database, config) {
|
|
|
101
99
|
const source = database.getSource(filePath)
|
|
102
100
|
|
|
103
101
|
|
|
104
|
-
if (source.lastModified === Number(stat.mtimeMs.toFixed())) return { jobs: [] }
|
|
102
|
+
if (source && source.lastModified === Number(stat.mtimeMs.toFixed())) return { jobs: [] }
|
|
103
|
+
|
|
104
|
+
database.deleteSettings(filePath)
|
|
105
105
|
|
|
106
106
|
// Get destination route
|
|
107
107
|
const destinationPath = route(filePath, plugin)
|
|
@@ -109,12 +109,17 @@ function readSourceFile(processors, database, config) {
|
|
|
109
109
|
// Set URL if exists
|
|
110
110
|
const buffer = await fs.readFile(path.format(fileInfo))
|
|
111
111
|
const data = decodeBuffer(buffer)
|
|
112
|
-
const { jobs, abstract, metadata } = read.text(data, database, config)
|
|
112
|
+
const { jobs, abstract, metadata } = read.text(data, filePath, database, config)
|
|
113
113
|
jobs && jobs.forEach(job => job.plugin = plugin.name)
|
|
114
|
-
// TODO: Cache: Check if file already exists
|
|
115
114
|
|
|
116
115
|
const timeStamp = stat.mtimeMs.toFixed()
|
|
117
|
-
|
|
116
|
+
|
|
117
|
+
if(source) {
|
|
118
|
+
database.updateSource(filePath, Number(timeStamp))
|
|
119
|
+
} else {
|
|
120
|
+
database.createSource(filePath, destinationPath, Number(timeStamp))
|
|
121
|
+
}
|
|
122
|
+
|
|
118
123
|
database.createOrUpdateDestination({
|
|
119
124
|
metadata,
|
|
120
125
|
path: destinationPath,
|
package/lib/utils/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { styleText } from "node:util"
|
|
2
|
+
import { statSync } from "node:fs"
|
|
2
3
|
|
|
3
4
|
/** @param {string} label */
|
|
4
5
|
export function stopwatch(label) {
|
|
@@ -20,3 +21,12 @@ export function splitURL(urlPath) {
|
|
|
20
21
|
const [urlFileName, ...urlDirSegmentsReversed] = urlPath.split("/").reverse()
|
|
21
22
|
return [urlDirSegmentsReversed.reverse().join("/") || "/", urlFileName]
|
|
22
23
|
}
|
|
24
|
+
|
|
25
|
+
/** @param {string} dbPath */
|
|
26
|
+
export function checkFile(dbPath) {
|
|
27
|
+
try {
|
|
28
|
+
return statSync(path.normalize(dbPath))
|
|
29
|
+
} catch (e) {
|
|
30
|
+
return null
|
|
31
|
+
}
|
|
32
|
+
}
|
package/lib/writeDestinations.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "votive",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"description": "A file processor.",
|
|
5
5
|
"homepage": "https://github.com/samlfair/votive#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
"./internals": "./lib/index.js"
|
|
20
20
|
},
|
|
21
21
|
"scripts": {
|
|
22
|
-
"test": "node --test --watch tests/*.js"
|
|
22
|
+
"test": "node --test --watch tests/*.js",
|
|
23
|
+
"coverage": "node --test --experimental-test-coverage --watch tests/*.js"
|
|
23
24
|
},
|
|
24
25
|
"dependencies": {
|
|
25
26
|
"encoding-sniffer": "^0.2.1",
|
package/tests/.votive.db
CHANGED
|
Binary file
|
package/tests/index.js
CHANGED
|
@@ -5,148 +5,313 @@ import path from "node:path"
|
|
|
5
5
|
import * as votive from "votive/internals"
|
|
6
6
|
import { stopwatch } from "./../lib/utils/index.js"
|
|
7
7
|
import { readFolders, runJobs } from "../lib/index.js"
|
|
8
|
+
import { readdir, rm } from "node:fs/promises"
|
|
9
|
+
import { checkFile } from "./../lib/utils/index.js"
|
|
8
10
|
|
|
9
11
|
/** @import {VotiveConfig, VotivePlugin, FlatProcessors, VotiveProcessor, Runner, Job} from "./../lib/bundle.js" */
|
|
12
|
+
/** @import {ProcessorRead, ProcessorWrite, ReadAbstract, ReadText, ReadTextResult, ReadFolder} from "./../lib/bundle.js" */
|
|
10
13
|
|
|
11
14
|
process.chdir("./tests")
|
|
12
15
|
|
|
13
16
|
test("empty directory", async () => {
|
|
14
|
-
const
|
|
17
|
+
const tempDest = fs.mkdtempDisposableSync("destination-")
|
|
18
|
+
const tempSource = fs.mkdtempDisposableSync("source-")
|
|
15
19
|
|
|
16
20
|
try {
|
|
17
21
|
const stop = stopwatch("Bundle empty folder")
|
|
18
22
|
await votive.bundle({
|
|
19
|
-
sourceFolder:
|
|
20
|
-
destinationFolder:
|
|
23
|
+
sourceFolder: tempSource.path,
|
|
24
|
+
destinationFolder: tempDest.path,
|
|
25
|
+
plugins: []
|
|
21
26
|
})
|
|
22
27
|
|
|
23
28
|
stop()
|
|
24
29
|
|
|
25
|
-
const dir = fs.readdirSync(
|
|
30
|
+
const dir = fs.readdirSync(tempDest.path)
|
|
26
31
|
assert(dir.length === 0, "Destination directory is not empty.")
|
|
27
32
|
} catch (error) {
|
|
28
|
-
|
|
33
|
+
tempDest.remove()
|
|
34
|
+
tempSource.remove()
|
|
29
35
|
console.error(error)
|
|
30
36
|
|
|
31
37
|
}
|
|
32
38
|
|
|
33
|
-
|
|
39
|
+
tempDest.remove()
|
|
40
|
+
tempSource.remove()
|
|
34
41
|
})
|
|
35
42
|
|
|
36
|
-
test("
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
stop()
|
|
43
|
+
test("internals", async (t) => {
|
|
44
|
+
const dbExists = checkFile(".votive.db")
|
|
45
|
+
if (dbExists) await rm(".votive.db")
|
|
40
46
|
|
|
41
|
-
database.createSource('exampleSource', 'exampleDestination', 123)
|
|
42
|
-
const [source] = database.getAllSources()
|
|
43
|
-
|
|
44
|
-
assert(source.destination === 'exampleDestination')
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
test("read sources", async () => {
|
|
48
47
|
const temp = fs.mkdtempDisposableSync("destination-")
|
|
49
48
|
|
|
50
|
-
/** @
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
49
|
+
/** @returns {Job} */
|
|
50
|
+
function createExampleJob() {
|
|
51
|
+
return {
|
|
52
|
+
data: Math.floor(Math.random() * 1000),
|
|
53
|
+
runner: "exampleRunner"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
/** @type {ReadText} */
|
|
59
|
+
function exampleTextReader(text, filePath, database, config) {
|
|
60
|
+
const matches = text.match(/\b\w+\b/)
|
|
61
|
+
const title = matches ? matches[0] : "Untitled"
|
|
62
|
+
|
|
63
|
+
/** @type {ReadTextResult} */
|
|
64
|
+
return {
|
|
65
|
+
abstract: {
|
|
66
|
+
content: text,
|
|
67
|
+
},
|
|
68
|
+
metadata: {
|
|
69
|
+
title
|
|
70
|
+
},
|
|
71
|
+
jobs: [
|
|
72
|
+
createExampleJob()
|
|
73
|
+
]
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** @type {ReadAbstract} */
|
|
78
|
+
function exampleAbstractReader(abstract, database, config) {
|
|
79
|
+
abstract.exampleAppend = true
|
|
80
|
+
return { abstract, jobs: [createExampleJob()] }
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** @type {ReadFolder} */
|
|
84
|
+
function exampleFolderReader(folder, database, config) {
|
|
85
|
+
return {
|
|
86
|
+
jobs: [createExampleJob()],
|
|
87
|
+
destinations: [
|
|
88
|
+
{
|
|
89
|
+
path: "/index.html",
|
|
90
|
+
abstract: { content: "" },
|
|
91
|
+
metadata: {
|
|
92
|
+
title: "home"
|
|
93
|
+
},
|
|
94
|
+
syntax: "md"
|
|
95
|
+
}
|
|
96
|
+
]
|
|
97
|
+
}
|
|
54
98
|
}
|
|
55
99
|
|
|
56
100
|
/** @type {VotiveProcessor} */
|
|
57
|
-
const
|
|
58
|
-
syntax: "
|
|
101
|
+
const exampleProcessor = {
|
|
102
|
+
syntax: "md",
|
|
59
103
|
filter: {
|
|
60
104
|
extensions: [".md"]
|
|
61
105
|
},
|
|
62
106
|
read: {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
folder: (folder, database, config) => [testJob]
|
|
107
|
+
text: exampleTextReader,
|
|
108
|
+
abstract: exampleAbstractReader,
|
|
109
|
+
folder: exampleFolderReader
|
|
67
110
|
},
|
|
68
111
|
write: (destination, database, config) => {
|
|
69
112
|
return {
|
|
70
|
-
data: "
|
|
113
|
+
data: "lorem ipsum",
|
|
71
114
|
}
|
|
72
115
|
}
|
|
73
116
|
}
|
|
74
117
|
|
|
118
|
+
/** @param {string} sourcePath */
|
|
119
|
+
function router(sourcePath) {
|
|
120
|
+
const { base, ...parsed }= path.parse(sourcePath)
|
|
121
|
+
return path.format({ ...parsed, ext: ".html" })
|
|
122
|
+
}
|
|
123
|
+
|
|
75
124
|
/** @type {VotivePlugin} */
|
|
76
|
-
const
|
|
77
|
-
name: "
|
|
125
|
+
const examplePlugin = {
|
|
126
|
+
name: "example plugin",
|
|
78
127
|
runners: {
|
|
79
|
-
|
|
128
|
+
exampleRunner: exampleRunner
|
|
80
129
|
},
|
|
81
|
-
router
|
|
82
|
-
processors: [
|
|
130
|
+
router,
|
|
131
|
+
processors: [exampleProcessor]
|
|
83
132
|
}
|
|
84
133
|
|
|
85
134
|
/** @type {Runner} */
|
|
86
|
-
async function
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
return waiting
|
|
135
|
+
async function exampleRunner(data, database) {
|
|
136
|
+
return await new Promise((resolve) => setTimeout(() => resolve(data), 1))
|
|
90
137
|
}
|
|
91
138
|
|
|
92
139
|
/** @type {VotiveConfig} */
|
|
93
140
|
const config = {
|
|
94
141
|
sourceFolder: "./markdown",
|
|
95
142
|
destinationFolder: temp.path,
|
|
96
|
-
plugins: [
|
|
143
|
+
plugins: [examplePlugin]
|
|
97
144
|
}
|
|
98
145
|
|
|
99
146
|
/** @type {FlatProcessors} */
|
|
100
147
|
const processors = [
|
|
101
148
|
{
|
|
102
|
-
plugin:
|
|
103
|
-
processor:
|
|
149
|
+
plugin: examplePlugin,
|
|
150
|
+
processor: exampleProcessor
|
|
104
151
|
}
|
|
105
152
|
]
|
|
106
153
|
|
|
107
154
|
try {
|
|
155
|
+
fs.writeFileSync("markdown/prunee.md", "A little content")
|
|
156
|
+
|
|
108
157
|
const database = votive.createDatabase()
|
|
158
|
+
const sourcesOne = database.getAllSources()
|
|
159
|
+
|
|
160
|
+
t.test("no sources on startup", () => {
|
|
161
|
+
const isArray = Array.isArray(sourcesOne)
|
|
162
|
+
const isEmpty = !sourcesOne.length
|
|
163
|
+
assert(isArray && isEmpty)
|
|
164
|
+
})
|
|
165
|
+
|
|
109
166
|
|
|
110
|
-
const moresources = database.getAllSources()
|
|
111
|
-
console.log({ moresources })
|
|
112
167
|
|
|
113
168
|
const stop = stopwatch("Read sources")
|
|
169
|
+
|
|
114
170
|
const { folders, sources } = await votive.readSources(config, database, processors)
|
|
171
|
+
|
|
172
|
+
t.test('one folder exists', () => {
|
|
173
|
+
assert(folders.length === 1)
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
t.test('three sources exist', () => {
|
|
177
|
+
assert(sources.length === 4)
|
|
178
|
+
})
|
|
179
|
+
|
|
115
180
|
stop()
|
|
116
181
|
|
|
117
182
|
const sourcesJobs = sources.flatMap(source => source.jobs)
|
|
183
|
+
|
|
118
184
|
const { processedAbstracts, abstractsJobs } = votive.readAbstracts(sources, config, database, processors)
|
|
119
185
|
|
|
186
|
+
t.test('transformation succeeded', () => {
|
|
187
|
+
assert(processedAbstracts.find(abstract => abstract.exampleAppend))
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
t.test('abstract jobs exist', () => {
|
|
191
|
+
assert(abstractsJobs.length > 0)
|
|
192
|
+
assert(abstractsJobs.every(job => job.data && job.runner))
|
|
193
|
+
})
|
|
194
|
+
|
|
120
195
|
const foldersJobs = readFolders(folders, config, database, processors)
|
|
121
196
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
197
|
+
t.test("folders jobs exist", () => {
|
|
198
|
+
assert(foldersJobs.length > 0)
|
|
199
|
+
assert(foldersJobs.every(job => job.data && job.runner))
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
database.createOrUpdateDestination({ metadata: { a: 1, b: 2 }, path: "abc.html", abstract: { c: 3 }, syntax: "md" })
|
|
203
|
+
const firstDestination = database.getDestinationIndependently("abc.html", [])
|
|
204
|
+
|
|
205
|
+
t.test('first destination created', () => {
|
|
206
|
+
assert(firstDestination.path === 'abc.html')
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
database.createOrUpdateDestination({ metadata: { a: 3, b: 4 }, path: "abc/def.html", abstract: { c: 5 }, syntax: "md" })
|
|
210
|
+
const destination = database.getDestinationDependently("abc.html", ["a"], "abc/def.html")
|
|
127
211
|
|
|
212
|
+
const dependencies = database.getDependencies()
|
|
213
|
+
|
|
214
|
+
t.test('dependency created', () => {
|
|
215
|
+
assert(dependencies[0].dependent === 'abc/def.html')
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
const setting = database.setSetting("abc.html", "theme", "blue", "markdown/prunee.md")
|
|
220
|
+
const newSetting = database.setSetting("abc", "category", "Dog", "markdown/prunee.md")
|
|
221
|
+
const sameSetting = database.setSetting("abc", "category", "Dog", "markdown/prunee.md")
|
|
222
|
+
const folderSetting = database.setSetting("abc", "theme", "red", "markdown/prunee.md")
|
|
223
|
+
|
|
224
|
+
t.test('settings created', () => {
|
|
225
|
+
assert(setting.value === 'blue')
|
|
226
|
+
assert(newSetting.value === 'Dog')
|
|
227
|
+
assert(!sameSetting)
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
const abcSettings = database.getSettings("abc.html")
|
|
231
|
+
|
|
232
|
+
t.test('settings retrieved', () => {
|
|
233
|
+
assert(abcSettings.theme[0] === "blue")
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
const descendentSetting = database.setSetting("abc/def.html", "theme", "green", "abc.md")
|
|
237
|
+
const defSettings = database.getSettings("abc/def.html")
|
|
238
|
+
|
|
239
|
+
t.test('settings retrieved', () => {
|
|
240
|
+
assert(abcSettings.theme[0] === "blue")
|
|
241
|
+
assert(defSettings.theme[1] === "green")
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
const staleDestinations = database.getStaleDestinations()
|
|
245
|
+
|
|
246
|
+
t.test('all destinations are stale', () => {
|
|
247
|
+
assert(staleDestinations.length === 7)
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
const written = await votive.writeDestinations(config, database)
|
|
251
|
+
|
|
252
|
+
const staleDestinationsAfterWriting = database.getStaleDestinations()
|
|
253
|
+
|
|
254
|
+
t.test('all destinations are fresh', () => {
|
|
255
|
+
assert(staleDestinationsAfterWriting.length === 0)
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
const updated = database.createOrUpdateDestination({ metadata: { a: 1, b: 9 }, path: "abc.html", abstract: { c: 4 }, syntax: "md" })
|
|
259
|
+
const staleAfterAbstractUpdate = database.getStaleDestinations()
|
|
260
|
+
|
|
261
|
+
t.test('updated document with no side effects is stale', () => {
|
|
262
|
+
assert(staleAfterAbstractUpdate.length === 1)
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
await votive.writeDestinations(config, database)
|
|
266
|
+
const staleAfterSecondWrite = database.getStaleDestinations()
|
|
267
|
+
|
|
268
|
+
t.test('everything fresh again', () => {
|
|
269
|
+
assert(staleAfterSecondWrite.length === 0)
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
const updatedWithSideEffects = database.createOrUpdateDestination({ metadata: { a: 4, b: 9 }, path: "abc.html", abstract: { c: 4 }, syntax: "md" })
|
|
273
|
+
const staleAfterSideEffects = database.getStaleDestinations()
|
|
274
|
+
|
|
275
|
+
t.test('side effects work', () => {
|
|
276
|
+
assert(staleAfterSideEffects.length === 2)
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
const freshDestinations = database.getAllDestinations()
|
|
280
|
+
|
|
281
|
+
await votive.writeDestinations(config, database)
|
|
128
282
|
|
|
129
|
-
const setting = database.setSetting("abc", "theme", "blue")
|
|
130
|
-
const newSetting = database.setSetting("def", "category", "Dog")
|
|
131
|
-
const sameSetting = database.setSetting("def", "category", "Dog")
|
|
132
283
|
const oldSetting = database.setSetting("abc", "theme", "green")
|
|
133
284
|
|
|
134
|
-
const
|
|
285
|
+
const staleAfterSettingsChange = database.getStaleDestinations()
|
|
286
|
+
|
|
287
|
+
t.test('setting affects descendents', () => {
|
|
288
|
+
assert(staleAfterSettingsChange.length === 1)
|
|
289
|
+
})
|
|
135
290
|
|
|
291
|
+
// TODO: Write tests for jobs
|
|
136
292
|
const jobs = [...sourcesJobs, ...abstractsJobs, ...foldersJobs]
|
|
137
293
|
runJobs(jobs, config, database)
|
|
138
294
|
|
|
139
|
-
const
|
|
295
|
+
const destinations = database.getAllDestinations()
|
|
296
|
+
database.getDestinationDependently("markdown/prunee.html", ["title"], "abc/def.html")
|
|
297
|
+
const newDependencies = database.getDependencies()
|
|
140
298
|
|
|
141
|
-
|
|
299
|
+
fs.rmSync("markdown/prunee.md")
|
|
300
|
+
|
|
301
|
+
await votive.pruneSources(config, database)
|
|
142
302
|
|
|
143
|
-
|
|
144
|
-
|
|
303
|
+
const result = database.getDestinationIndependently("markdown/prunee.html")
|
|
304
|
+
const prunedDependencies = database.getDependencies()
|
|
305
|
+
const prunedMetadata = database.getAllMetadataByPath("markdown/prunee.html")
|
|
306
|
+
|
|
307
|
+
t.test('source pruned', () => {
|
|
308
|
+
assert(!result)
|
|
309
|
+
assert(newDependencies.length - prunedDependencies.length === 1)
|
|
310
|
+
assert(!prunedMetadata)
|
|
311
|
+
})
|
|
145
312
|
|
|
146
|
-
|
|
147
|
-
console.log({ newsources })
|
|
313
|
+
await database.saveDB()
|
|
148
314
|
|
|
149
|
-
assert(true, "Read test tk")
|
|
150
315
|
temp.remove()
|
|
151
316
|
} catch (error) {
|
|
152
317
|
temp.remove()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
This is an index root.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|