votive 0.0.1 → 0.0.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/README.md +3 -0
- package/jsconfig.json +10 -0
- package/lib/bundle.js +178 -0
- package/lib/createDatabase.js +323 -0
- package/lib/index.js +7 -0
- package/lib/readAbstracts.js +59 -0
- package/lib/readFolders.js +28 -0
- package/lib/readSources.js +142 -0
- package/lib/runJobs.js +23 -0
- package/lib/utils/index.js +22 -0
- package/lib/writeDestinations.js +42 -0
- package/package.json +24 -6
- package/tests/.votive.db +0 -0
- package/tests/index.js +155 -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/index.js +0 -1
package/README.md
ADDED
package/jsconfig.json
ADDED
package/lib/bundle.js
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { default as createDatabase } from "./createDatabase.js"
|
|
2
|
+
import readSources from "./readSources.js"
|
|
3
|
+
import readAbstracts from "./readAbstracts.js"
|
|
4
|
+
import readFolders from "./readFolders.js"
|
|
5
|
+
import writeDestinations from "./writeDestinations.js"
|
|
6
|
+
import runJobs from "./runJobs.js"
|
|
7
|
+
|
|
8
|
+
/** @import {Database} from "./createDatabase.js" */
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {object} VotivePlugin
|
|
12
|
+
* @property {string} name
|
|
13
|
+
* @property {VotiveProcessor[]} [processors]
|
|
14
|
+
* @property {Record<string, Runner>} [runners]
|
|
15
|
+
* @property {Router} [router]
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @typedef {object} VotiveProcessor
|
|
20
|
+
* @property {ProcessorFilter} [filter]
|
|
21
|
+
* @property {ProcessorSyntax} syntax
|
|
22
|
+
* @property {ProcessorRead} [read]
|
|
23
|
+
* @property {ProcessorWrite} [write]
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Filter for files that the processor will read from.
|
|
28
|
+
* @typedef {object} ProcessorFilter
|
|
29
|
+
* @property {string[]} extensions
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* A name for the syntax that processors will read-to and write-from (e.g. Unified.js's "hast").
|
|
34
|
+
* @typedef {string} ProcessorSyntax
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @typedef {object} ProcessorRead
|
|
39
|
+
* @property {ReadPath} [path]
|
|
40
|
+
* @property {ReadText} [text]
|
|
41
|
+
* @property {ReadAbstract} [abstract]
|
|
42
|
+
* @property {ReadFolder} [folder]
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Read a file path and return any necessary jobs.
|
|
47
|
+
* @callback ReadPath
|
|
48
|
+
* @param {string} filePath
|
|
49
|
+
* @param {Database} database
|
|
50
|
+
* @param {VotiveConfig} config
|
|
51
|
+
* @returns {Job[] | undefined}
|
|
52
|
+
*/
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @callback ReadText
|
|
56
|
+
* @param {string} text
|
|
57
|
+
* @param {Database} database
|
|
58
|
+
* @param {VotiveConfig} config
|
|
59
|
+
* @returns {ReadTextResult | undefined}
|
|
60
|
+
*/
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* @typedef {object} ReadTextResult
|
|
64
|
+
* @property {Jobs} [jobs]
|
|
65
|
+
* @property {object} [metadata]
|
|
66
|
+
* @property {Abstract} [abstract]
|
|
67
|
+
*/
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* @typedef {Job[]} Jobs
|
|
71
|
+
*/
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @callback ReadAbstract
|
|
75
|
+
* @param {Abstract} abstract
|
|
76
|
+
* @param {Database} database
|
|
77
|
+
* @param {VotiveConfig} config
|
|
78
|
+
* @returns {{abstract: Abstract, jobs: Jobs}}
|
|
79
|
+
*/
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* @typedef {Record<string, any>} Abstract
|
|
83
|
+
*/
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* @typedef {Abstract[]} Abstracts
|
|
87
|
+
*/
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* @typedef {AbstractWithSyntax[]} AbstractsWithSyntax
|
|
91
|
+
*/
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @typedef {object} AbstractWithSyntax
|
|
95
|
+
* @property {Abstract} abstract
|
|
96
|
+
* @property {ProcessorSyntax} syntax
|
|
97
|
+
*/
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* @callback ReadFolder
|
|
101
|
+
* @param {object} Folder
|
|
102
|
+
* @param {Database} database
|
|
103
|
+
* @param {VotiveConfig} config
|
|
104
|
+
*/
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* @typedef {object} Folder
|
|
108
|
+
* @property {string} path
|
|
109
|
+
*/
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Build and write destination files.
|
|
113
|
+
* @callback ProcessorWrite
|
|
114
|
+
* @param {object} destination
|
|
115
|
+
* @param {Database} database
|
|
116
|
+
* @param {VotiveConfig} config
|
|
117
|
+
* @returns {{ data: string, encoding?: BufferEncoding = 'utf-8' }}
|
|
118
|
+
*/
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* A function that runs a job, suggest as fetching data or formatting an image.
|
|
122
|
+
* @callback Runner
|
|
123
|
+
* @param {string} file
|
|
124
|
+
* @param {Database} database
|
|
125
|
+
* @returns {Promise<any>}
|
|
126
|
+
*/
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Run a job.
|
|
130
|
+
* @typedef {object} Job
|
|
131
|
+
* @property {object} data
|
|
132
|
+
* @property {string} runner
|
|
133
|
+
* @property {string} [plugin]
|
|
134
|
+
*/
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* @callback Router
|
|
138
|
+
* @param {string} path
|
|
139
|
+
* @returns {string | false | undefined}
|
|
140
|
+
*/
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* @typedef {object} VotiveConfig
|
|
144
|
+
* @property {string} sourceFolder
|
|
145
|
+
* @property {string} destinationFolder
|
|
146
|
+
* @property {VotivePlugin[]} plugins
|
|
147
|
+
*/
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* @typedef {object} FlatProcessor
|
|
151
|
+
* @property {VotivePlugin} plugin
|
|
152
|
+
* @property {VotiveProcessor} processor
|
|
153
|
+
*/
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* @typedef {FlatProcessor[]} FlatProcessors
|
|
157
|
+
*/
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* @param {VotiveConfig} config
|
|
161
|
+
*/
|
|
162
|
+
async function bundle(config) {
|
|
163
|
+
const processors = config.plugins
|
|
164
|
+
&& config.plugins.flatMap(plugin => plugin.processors && plugin.processors.map(processor => ({ plugin, processor })))
|
|
165
|
+
|
|
166
|
+
const database = createDatabase()
|
|
167
|
+
const { folders, sources } = await readSources(config, database, processors)
|
|
168
|
+
|
|
169
|
+
const sourcesJobs = sources.flatMap(source => source.jobs) || []
|
|
170
|
+
const { processedAbstracts, abstractsJobs } = readAbstracts(sources, config, database, processors)
|
|
171
|
+
const foldersJobs = readFolders(folders, config, database, processors) || []
|
|
172
|
+
await writeDestinations(config, database)
|
|
173
|
+
|
|
174
|
+
const jobs = [...sourcesJobs, ...abstractsJobs, ...foldersJobs]
|
|
175
|
+
await runJobs(jobs, config, database)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export default bundle
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import { DatabaseSync, backup } from "node:sqlite"
|
|
2
|
+
import { splitURL } from "./utils/index.js"
|
|
3
|
+
import { statSync } from "node:fs"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {ReturnType<createDatabase>} Database
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
function checkFile(path) {
|
|
11
|
+
try {
|
|
12
|
+
return statSync(path)
|
|
13
|
+
} catch(e) {
|
|
14
|
+
return null
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
function createDatabase() {
|
|
20
|
+
const databaseSync = new DatabaseSync(".votive.db")
|
|
21
|
+
|
|
22
|
+
const store = databaseSync.createTagStore()
|
|
23
|
+
|
|
24
|
+
databaseSync.exec(sqlCreateTables)
|
|
25
|
+
|
|
26
|
+
const database = {}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @param {string} folderPath
|
|
30
|
+
* @param {string} urlPath
|
|
31
|
+
*/
|
|
32
|
+
database.createFolder = (folderPath, urlPath) => {
|
|
33
|
+
return store.get`INSERT INTO folders (folderPath, urlPath) VALUES :${folderPath} :${urlPath} RETURNING *`
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const createSource = databaseSync.prepare(`INSERT INTO sources (path, destination, lastModified) VALUES (?, ?, ?)`)
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @param {string} source - Source file path.
|
|
40
|
+
* @param {string} destination - Destination file path.
|
|
41
|
+
* @param {number} lastModified - Source file date last modified.
|
|
42
|
+
*/
|
|
43
|
+
database.createSource = (source, destination, lastModified) => {
|
|
44
|
+
databaseSync.prepare(``) // SQLite bug. Query fails without this.
|
|
45
|
+
const inserted = createSource.get(source, destination, lastModified)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
// These statements are cached, no need to refactor
|
|
50
|
+
const createDest = databaseSync.prepare(`INSERT INTO destinations (path, dir, syntax, stale, abstract) VALUES (?, ?, ?, 1, ?)`)
|
|
51
|
+
const updateDest = databaseSync.prepare(`UPDATE destinations SET abstract = ? WHERE path = ?`)
|
|
52
|
+
const createMeta = databaseSync.prepare(`INSERT INTO metadata (destination, label, value, type) VALUES (?, ?, ?, ?)`)
|
|
53
|
+
const updateMeta = databaseSync.prepare(`UPDATE metadata SET value = ? WHERE destination = ? AND label = ?`)
|
|
54
|
+
const getDepends = databaseSync.prepare(`SELECT * FROM dependencies WHERE destination = ? AND property = ?`)
|
|
55
|
+
const staleDepen = databaseSync.prepare(`UPDATE destinations SET stale = 1 WHERE path = ?`)
|
|
56
|
+
const staleDescendents = databaseSync.prepare(`UPDATE destinations SET stale = 1 WHERE path LIKE ? RETURNING *`)
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @param {object} params
|
|
60
|
+
* @param {string} params.path - Destination file path
|
|
61
|
+
* @param {object} params.abstract
|
|
62
|
+
* @param {object} params.metadata
|
|
63
|
+
* @param {string} params.syntax - Destination abstract syntax
|
|
64
|
+
*/
|
|
65
|
+
database.createOrUpdateDestination = ({ metadata, ...dest }) => {
|
|
66
|
+
const [dir] = dest.path ? splitURL(dest.path) : []
|
|
67
|
+
const params = Object.keys(metadata)
|
|
68
|
+
if (dest.abstract) params.push("abstract")
|
|
69
|
+
const extant = database.getDestinationIndependently(dest.path, params)
|
|
70
|
+
|
|
71
|
+
if (!extant) {
|
|
72
|
+
createDest.get(dest.path, dir, dest.syntax, JSON.stringify(dest.abstract))
|
|
73
|
+
Object.entries(metadata).map(([k, v]) => createMeta.get(dest.path, k, v, typeof v))
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const changedAbstract = JSON.stringify(dest.abstract) !== JSON.stringify(extant.abstract)
|
|
78
|
+
const changedMetadata = []
|
|
79
|
+
|
|
80
|
+
for (const key in metadata) {
|
|
81
|
+
if (JSON.stringify(metadata[key]) !== JSON.stringify(extant.metadata[key])) {
|
|
82
|
+
updateMeta.get(metadata[key], dest.path, key)
|
|
83
|
+
const dependencies = getDepends.all(dest.path, key)
|
|
84
|
+
dependencies.forEach(({ dependent }) => {
|
|
85
|
+
staleDepen.get(dependent)
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (changedAbstract) {
|
|
91
|
+
const updated = updateDest.get(JSON.stringify(dest.abstract), dest.path)
|
|
92
|
+
const dependencies = getDepends.all(dest.path, "abstract")
|
|
93
|
+
dependencies.forEach(({ dependent }) => {
|
|
94
|
+
staleDepen.get(dependent)
|
|
95
|
+
})
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* @param {string} path
|
|
101
|
+
* @param {string[]} params
|
|
102
|
+
*/
|
|
103
|
+
database.getDestinationIndependently = (path, params) => {
|
|
104
|
+
// TODO: Could potentially speed up the next two db queries by using the tag store
|
|
105
|
+
const metadatas = databaseSync.prepare(`
|
|
106
|
+
SELECT * FROM destinations d
|
|
107
|
+
LEFT JOIN metadata m ON d.path = m.destination AND m.label IN (${params.map(p => `'${p}'`).join(", ")})
|
|
108
|
+
WHERE d.path = ?
|
|
109
|
+
`).all(path)
|
|
110
|
+
|
|
111
|
+
const [first] = metadatas
|
|
112
|
+
|
|
113
|
+
if (!first) return
|
|
114
|
+
|
|
115
|
+
const metadata = Object.fromEntries(
|
|
116
|
+
metadatas.map(({ label, value, type }) => {
|
|
117
|
+
const originalValue = type === "undefined"
|
|
118
|
+
? undefined
|
|
119
|
+
: type === "string"
|
|
120
|
+
? String(value)
|
|
121
|
+
: type === "boolean"
|
|
122
|
+
? Boolean(value)
|
|
123
|
+
: typeof value !== "string"
|
|
124
|
+
? value
|
|
125
|
+
: JSON.parse(value)
|
|
126
|
+
return [label, value]
|
|
127
|
+
})
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
const result = {
|
|
132
|
+
path: first.path,
|
|
133
|
+
dir: first.dir,
|
|
134
|
+
syntax: first.syntax,
|
|
135
|
+
metadata
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const includeAbstract = params.includes("abstract")
|
|
139
|
+
if (includeAbstract) result.abstract = JSON.parse(first.abstract)
|
|
140
|
+
|
|
141
|
+
return result
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* @param {string} path
|
|
146
|
+
* @param {string[]} properties
|
|
147
|
+
* @param {string} dependent
|
|
148
|
+
*/
|
|
149
|
+
database.getDestinationDependently = (path, properties, dependent) => {
|
|
150
|
+
const params = []
|
|
151
|
+
const deps = []
|
|
152
|
+
|
|
153
|
+
properties.forEach(p => {
|
|
154
|
+
params.push(p)
|
|
155
|
+
deps.push(`('${path}', '${p}', '${dependent}')`)
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
const result = database.getDestinationIndependently(path, params)
|
|
159
|
+
if (result.abstract) deps.push(`('${path}, 'abstract', '${dependent}')`)
|
|
160
|
+
const createDepp = databaseSync.prepare(`INSERT INTO dependencies (destination, property, dependent) VALUES ${deps.join(", ")}`).all()
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const selectSource = databaseSync.prepare(`SELECT * FROM sources WHERE path = ?`)
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* @param {string} path
|
|
167
|
+
*/
|
|
168
|
+
database.getSource = (path) => {
|
|
169
|
+
const source = selectSource.get(path)
|
|
170
|
+
return source
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
database.getAllSources = () => {
|
|
174
|
+
const sources = store.all`SELECT * FROM sources`
|
|
175
|
+
return sources
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
database.getAllDestinations = () => {
|
|
179
|
+
return store.all`SELECT * FROM destinations`
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
database.getStaleDestinations = () => {
|
|
183
|
+
return store.all`SELECT * FROM destinations WHERE stale = 1`
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const createSetting = databaseSync.prepare(`INSERT INTO settings (destination, label, value) VALUES (?, ?, ?) RETURNING *`)
|
|
187
|
+
const selectSetting = databaseSync.prepare(`SELECT * FROM settings WHERE label = ? AND destination = ?`)
|
|
188
|
+
const updateSetting = databaseSync.prepare(`UPDATE settings SET value = ? WHERE destination = ? AND label = ?`)
|
|
189
|
+
|
|
190
|
+
// const updateDest = databaseSync.prepare(`UPDATE destinations SET abstract = ? WHERE path = ?`)
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* @param {string} destination
|
|
194
|
+
* @param {string} key
|
|
195
|
+
* @param {string} value
|
|
196
|
+
*/
|
|
197
|
+
database.setSetting = (destination, key, value) => {
|
|
198
|
+
// TODO: Check if already exists
|
|
199
|
+
const extant = selectSetting.get(key, destination)
|
|
200
|
+
|
|
201
|
+
if (!extant) return createSetting.get(destination, key, value)
|
|
202
|
+
if (extant.value === value) return
|
|
203
|
+
|
|
204
|
+
updateSetting.get(value, destination, key)
|
|
205
|
+
const stale = staleDescendents.all(`${destination}/%`)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* @param {string} destination
|
|
211
|
+
* @param {string} dependent
|
|
212
|
+
*/
|
|
213
|
+
database.getSettings = (destination, dependent) => {
|
|
214
|
+
return Object.fromEntries(databaseSync.prepare(`SELECT * FROM settings WHERE destination = '${destination}' OR destination LIKE '${destination}/%';`)
|
|
215
|
+
.all()
|
|
216
|
+
.map(({ label, value }) => [label, value]))
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
database.getEverything = () => {
|
|
220
|
+
return Object.fromEntries(
|
|
221
|
+
databaseSync.prepare(`SELECT name FROM sqlite_master WHERE type = 'table'`)
|
|
222
|
+
.all()
|
|
223
|
+
.map(table => [
|
|
224
|
+
table.name,
|
|
225
|
+
databaseSync.prepare(`SELECT * FROM ${table.name}`).all()
|
|
226
|
+
])
|
|
227
|
+
)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return database
|
|
231
|
+
}
|
|
232
|
+
|
|
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
|
+
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
|
+
CREATE TABLE IF NOT EXISTS sources (
|
|
278
|
+
id INTEGER PRIMARY KEY,
|
|
279
|
+
destination STRING,
|
|
280
|
+
path STRING,
|
|
281
|
+
lastModified INTEGER
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
CREATE TABLE IF NOT EXISTS dependencies (
|
|
285
|
+
key INTEGER PRIMARY KEY,
|
|
286
|
+
destination STRING NOT NULL,
|
|
287
|
+
property STRING NOT NULL,
|
|
288
|
+
dependent STRING NOT NULL
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
CREATE TABLE IF NOT EXISTS destinations (
|
|
292
|
+
key INTEGER PRIMARY KEY,
|
|
293
|
+
path STRING UNIQUE,
|
|
294
|
+
dir TEXT,
|
|
295
|
+
syntax TEXT,
|
|
296
|
+
stale INTEGER,
|
|
297
|
+
abstract
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
CREATE TABLE IF NOT EXISTS folders (
|
|
301
|
+
path STRING PRIMARY KEY,
|
|
302
|
+
urlPath STRING
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
CREATE TABLE IF NOT EXISTS metadata (
|
|
306
|
+
id INTEGER PRIMARY KEY,
|
|
307
|
+
destination STRING,
|
|
308
|
+
label STRING,
|
|
309
|
+
value STRING,
|
|
310
|
+
type STRING,
|
|
311
|
+
UNIQUE(destination, label)
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
CREATE TABLE IF NOT EXISTS settings (
|
|
315
|
+
id INTEGER PRIMARY KEY,
|
|
316
|
+
destination STRING,
|
|
317
|
+
label STRING,
|
|
318
|
+
value STRING
|
|
319
|
+
);
|
|
320
|
+
|
|
321
|
+
`
|
|
322
|
+
|
|
323
|
+
export default createDatabase
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { default as bundle } from "./bundle.js"
|
|
2
|
+
export { default as createDatabase } from "./createDatabase.js"
|
|
3
|
+
export { default as readAbstracts } from "./readAbstracts.js"
|
|
4
|
+
export { default as readFolders } from "./readFolders.js"
|
|
5
|
+
export { default as readSources } from "./readSources.js"
|
|
6
|
+
export { default as runJobs } from "./runJobs.js"
|
|
7
|
+
export { default as writeDestinations } from "./writeDestinations.js"
|
|
@@ -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 readAbstracts(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.plugin = plugin.name)
|
|
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, job}) => {
|
|
52
|
+
processedAbstracts.push(abstract)
|
|
53
|
+
abstractsJobs.push(job)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
return { processedAbstracts, abstractsJobs }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export default readAbstracts
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** @import {VotiveConfig, FlatProcessors, Jobs} from "./bundle.js" */
|
|
2
|
+
/** @import {Database} from "./createDatabase.js" */
|
|
3
|
+
/** @import {Dirent} from "node:fs" */
|
|
4
|
+
|
|
5
|
+
import path from "node:path"
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @param {Dirent[]} folders
|
|
9
|
+
* @param {VotiveConfig} config
|
|
10
|
+
* @param {Database} database
|
|
11
|
+
* @param {FlatProcessors} processors
|
|
12
|
+
* @returns {Jobs}
|
|
13
|
+
*/
|
|
14
|
+
function readFolders(folders, config, database, processors) {
|
|
15
|
+
if(!folders) return
|
|
16
|
+
return folders.flatMap(folder => {
|
|
17
|
+
return config.plugins.flatMap(plugin => {
|
|
18
|
+
return plugin.processors.flatMap(processor => {
|
|
19
|
+
const folderPath = path.relative(config.sourceFolder, path.join(folder.parentPath, folder.name))
|
|
20
|
+
return processor.read
|
|
21
|
+
&& processor.read.folder
|
|
22
|
+
&& processor.read.folder(folderPath, database, config)
|
|
23
|
+
})
|
|
24
|
+
})
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default readFolders
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { decodeBuffer } from "encoding-sniffer"
|
|
2
|
+
import fs from "node:fs/promises"
|
|
3
|
+
import path from "node:path"
|
|
4
|
+
import { visit } from "unist-util-visit"
|
|
5
|
+
|
|
6
|
+
/** @import {VotiveConfig, VotivePlugin, VotiveProcessor, FlatProcessors, Abstracts, Abstract, Jobs, ProcessorSyntax} from "./bundle.js" */
|
|
7
|
+
/** @import {Dirent} from "node:fs" */
|
|
8
|
+
/** @import {Database} from "./createDatabase.js" */
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {object} ReadSourcesResult
|
|
12
|
+
* @property {Dirent[]} folders
|
|
13
|
+
* @property {ReadSourceFilePlugin[]} sources
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @param {VotiveConfig} config
|
|
18
|
+
* @param {Database} database
|
|
19
|
+
* @param {FlatProcessors} processors
|
|
20
|
+
* @returns {Promise<ReadSourcesResult>}
|
|
21
|
+
*/
|
|
22
|
+
async function readSources(config, database, processors) {
|
|
23
|
+
const dirents = await fs.readdir(config.sourceFolder, {
|
|
24
|
+
withFileTypes: true,
|
|
25
|
+
recursive: true
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
// TODO: Check file modified time
|
|
29
|
+
|
|
30
|
+
const filteredDirents = (dirents || []).filter(fileFilter(config, database))
|
|
31
|
+
const { files, folders } = Object.groupBy(filteredDirents, (dirent) => dirent.isFile() ? "files" : "folders")
|
|
32
|
+
if (!files) return { folders, sources: [] }
|
|
33
|
+
const readingSourceFiles = files.flatMap(readSourceFile(processors, database, config))
|
|
34
|
+
const sources = readingSourceFiles && await Promise.all(readingSourceFiles)
|
|
35
|
+
return {
|
|
36
|
+
folders,
|
|
37
|
+
sources
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @param {VotiveConfig} config
|
|
43
|
+
* @param {Database} database
|
|
44
|
+
*/
|
|
45
|
+
function fileFilter(config, database) {
|
|
46
|
+
/** @param {Dirent} dirent */
|
|
47
|
+
return (dirent) => {
|
|
48
|
+
if (dirent.parentPath === config.destinationFolder) {
|
|
49
|
+
return false
|
|
50
|
+
} else if (dirent.parentPath.startsWith(config.destinationFolder + path.sep)) {
|
|
51
|
+
return false // Ignore destination folder
|
|
52
|
+
} else if (dirent.name.startsWith(".")) {
|
|
53
|
+
return false // Ignore hidden files
|
|
54
|
+
} else if (dirent.parentPath.includes(path.sep + ".")) {
|
|
55
|
+
return false // Ignore hidden folders
|
|
56
|
+
}
|
|
57
|
+
return true
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @typedef {ReadSourceFilePlugin[]} ReadSourceFileResult
|
|
63
|
+
*/
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @typedef {object} ReadSourceFilePlugin
|
|
67
|
+
* @property {Abstract} abstract
|
|
68
|
+
* @property {ProcessorSyntax} syntax
|
|
69
|
+
* @property {Jobs} jobs
|
|
70
|
+
* @property {string} destinationPath
|
|
71
|
+
*/
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @param {{ plugin: VotivePlugin, processor: VotiveProcessor }[]} processors
|
|
75
|
+
* @param {Database} database
|
|
76
|
+
* @param {VotiveConfig} config
|
|
77
|
+
*/
|
|
78
|
+
function readSourceFile(processors, database, config) {
|
|
79
|
+
/**
|
|
80
|
+
* @param {import("node:fs").Dirent} dirent
|
|
81
|
+
* @returns {Promise<ReadSourceFilePlugin>[]}
|
|
82
|
+
*/
|
|
83
|
+
return (dirent) => {
|
|
84
|
+
const { name, parentPath } = dirent
|
|
85
|
+
const filePath = path.join(parentPath, name)
|
|
86
|
+
const fileInfo = path.parse(filePath)
|
|
87
|
+
|
|
88
|
+
const processing = processors.flatMap(({ plugin, processor }) => process(plugin, processor, config))
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* @param {VotivePlugin} plugin
|
|
92
|
+
* @param {VotiveProcessor} processor
|
|
93
|
+
* @param {VotiveConfig} config
|
|
94
|
+
*/
|
|
95
|
+
async function process(plugin, processor, config) {
|
|
96
|
+
const { read, filter, syntax } = processor
|
|
97
|
+
if (filter.extensions.includes(fileInfo.ext) && read && read.text) {
|
|
98
|
+
|
|
99
|
+
// Check modified time
|
|
100
|
+
const stat = await fs.stat(filePath)
|
|
101
|
+
const source = database.getSource(filePath)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
if (source.lastModified === Number(stat.mtimeMs.toFixed())) return { jobs: [] }
|
|
105
|
+
|
|
106
|
+
// Get destination route
|
|
107
|
+
const destinationPath = route(filePath, plugin)
|
|
108
|
+
|
|
109
|
+
// Set URL if exists
|
|
110
|
+
const buffer = await fs.readFile(path.format(fileInfo))
|
|
111
|
+
const data = decodeBuffer(buffer)
|
|
112
|
+
const { jobs, abstract, metadata } = read.text(data, database, config)
|
|
113
|
+
jobs && jobs.forEach(job => job.plugin = plugin.name)
|
|
114
|
+
// TODO: Cache: Check if file already exists
|
|
115
|
+
|
|
116
|
+
const timeStamp = stat.mtimeMs.toFixed()
|
|
117
|
+
database.createSource(filePath, destinationPath, Number(timeStamp))
|
|
118
|
+
database.createOrUpdateDestination({
|
|
119
|
+
metadata,
|
|
120
|
+
path: destinationPath,
|
|
121
|
+
abstract: abstract,
|
|
122
|
+
syntax: syntax
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
return { abstract, destinationPath, syntax, jobs }
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return processing
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* @param {string} filePath
|
|
135
|
+
* @param {VotivePlugin} plugin
|
|
136
|
+
*/
|
|
137
|
+
function route(filePath, plugin) {
|
|
138
|
+
if (!plugin.router) return ""
|
|
139
|
+
return plugin.router(filePath) || ""
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export default readSources
|
package/lib/runJobs.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import workerpool from "workerpool"
|
|
2
|
+
|
|
3
|
+
/** @import {Job, Jobs, Database, VotiveConfig} from "./bundle.js" */
|
|
4
|
+
|
|
5
|
+
const pool = workerpool.pool()
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @param {Jobs} jobs
|
|
9
|
+
* @param {VotiveConfig} config
|
|
10
|
+
* @param {Database} database
|
|
11
|
+
*/
|
|
12
|
+
async function runJobs(jobs, config, database) {
|
|
13
|
+
const running = jobs.map(async job => {
|
|
14
|
+
if (!job) return
|
|
15
|
+
const plugin = config.plugins.find(plugin => plugin.name === job.plugin)
|
|
16
|
+
return pool.exec(plugin.runners[job.runner], [job.data])
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
const ran = await Promise.allSettled(running)
|
|
20
|
+
pool.terminate()
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default runJobs
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { styleText } from "node:util"
|
|
2
|
+
|
|
3
|
+
/** @param {string} label */
|
|
4
|
+
export function stopwatch(label) {
|
|
5
|
+
const start = performance.now()
|
|
6
|
+
|
|
7
|
+
function stop() {
|
|
8
|
+
const end = performance.now()
|
|
9
|
+
const duration = end - start
|
|
10
|
+
console.info(`${label}: ${styleText("red", duration.toFixed(2) + "ms")}`)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return stop
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @param {string} urlPath
|
|
18
|
+
*/
|
|
19
|
+
export function splitURL(urlPath) {
|
|
20
|
+
const [urlFileName, ...urlDirSegmentsReversed] = urlPath.split("/").reverse()
|
|
21
|
+
return [urlDirSegmentsReversed.reverse().join("/") || "/", urlFileName]
|
|
22
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises"
|
|
2
|
+
import path from "node:path"
|
|
3
|
+
|
|
4
|
+
/** @import {Abstract, Abstracts, VotiveConfig} from "./bundle.js" */
|
|
5
|
+
/** @import {Database} from "./createDatabase.js" */
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @param {VotiveConfig} config
|
|
10
|
+
* @param {Database} database
|
|
11
|
+
*/
|
|
12
|
+
async function writeDestinations(config, database) {
|
|
13
|
+
const writeProcessors = config && config.plugins && config.plugins.flatMap(plugin => (
|
|
14
|
+
plugin.processors && plugin.processors.map(({ write, syntax }) => write && { write, syntax }).filter(a => a)
|
|
15
|
+
)).filter(a => a)
|
|
16
|
+
|
|
17
|
+
if (!writeProcessors || !writeProcessors.length) throw "No write processor provided"
|
|
18
|
+
|
|
19
|
+
const destinations = database.getStaleDestinations()
|
|
20
|
+
if (!destinations) return
|
|
21
|
+
|
|
22
|
+
const writing = destinations.flatMap(destination => {
|
|
23
|
+
const destinationPath = path.join(config.destinationFolder, String(destination.path))
|
|
24
|
+
const { dir } = path.parse(destinationPath)
|
|
25
|
+
return writeProcessors.map(processor => {
|
|
26
|
+
if (processor.syntax === destination.syntax) {
|
|
27
|
+
const { data, encoding = 'utf-8' } = processor.write(destination, database, config)
|
|
28
|
+
|
|
29
|
+
async function write() {
|
|
30
|
+
await mkdir(dir, { recursive: true })
|
|
31
|
+
await writeFile(destinationPath, data, encoding)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return write()
|
|
35
|
+
}
|
|
36
|
+
})
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
await Promise.all(writing)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export default writeDestinations
|
package/package.json
CHANGED
|
@@ -1,12 +1,30 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "votive",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.0.3",
|
|
4
|
+
"description": "A file processor.",
|
|
5
|
+
"homepage": "https://github.com/samlfair/votive#readme",
|
|
6
|
+
"bugs": {
|
|
7
|
+
"url": "https://github.com/samlfair/votive/issues"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/samlfair/votive.git"
|
|
12
|
+
},
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"author": "Sam Littlefair",
|
|
15
|
+
"type": "module",
|
|
5
16
|
"main": "index.js",
|
|
6
|
-
"
|
|
7
|
-
|
|
8
|
-
|
|
17
|
+
"exports": {
|
|
18
|
+
".": "./lib/bundle.js",
|
|
19
|
+
"./internals": "./lib/index.js"
|
|
20
|
+
},
|
|
9
21
|
"scripts": {
|
|
10
|
-
"test": "
|
|
22
|
+
"test": "node --test --watch tests/*.js"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"encoding-sniffer": "^0.2.1",
|
|
26
|
+
"unified": "^11.0.5",
|
|
27
|
+
"unist-util-visit": "^5.0.0",
|
|
28
|
+
"workerpool": "^10.0.1"
|
|
11
29
|
}
|
|
12
30
|
}
|
package/tests/.votive.db
ADDED
|
Binary file
|
package/tests/index.js
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import test from "node:test"
|
|
2
|
+
import assert from "node:assert/strict"
|
|
3
|
+
import fs from "node:fs"
|
|
4
|
+
import path from "node:path"
|
|
5
|
+
import * as votive from "votive/internals"
|
|
6
|
+
import { stopwatch } from "./../lib/utils/index.js"
|
|
7
|
+
import { readFolders, runJobs } from "../lib/index.js"
|
|
8
|
+
|
|
9
|
+
/** @import {VotiveConfig, VotivePlugin, FlatProcessors, VotiveProcessor, Runner, Job} from "./../lib/bundle.js" */
|
|
10
|
+
|
|
11
|
+
process.chdir("./tests")
|
|
12
|
+
|
|
13
|
+
test("empty directory", async () => {
|
|
14
|
+
const temp = fs.mkdtempDisposableSync("destination-")
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
const stop = stopwatch("Bundle empty folder")
|
|
18
|
+
await votive.bundle({
|
|
19
|
+
sourceFolder: "./empty",
|
|
20
|
+
destinationFolder: temp.path
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
stop()
|
|
24
|
+
|
|
25
|
+
const dir = fs.readdirSync(temp.path)
|
|
26
|
+
assert(dir.length === 0, "Destination directory is not empty.")
|
|
27
|
+
} catch (error) {
|
|
28
|
+
temp.remove()
|
|
29
|
+
console.error(error)
|
|
30
|
+
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
temp.remove()
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
test("create empty database", () => {
|
|
37
|
+
const stop = stopwatch("Create empty database")
|
|
38
|
+
const database = votive.createDatabase()
|
|
39
|
+
stop()
|
|
40
|
+
|
|
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
|
+
const temp = fs.mkdtempDisposableSync("destination-")
|
|
49
|
+
|
|
50
|
+
/** @type {Job} */
|
|
51
|
+
const testJob = {
|
|
52
|
+
data: "123",
|
|
53
|
+
runner: "testRunner"
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** @type {VotiveProcessor} */
|
|
57
|
+
const testProcessor = {
|
|
58
|
+
syntax: "txt",
|
|
59
|
+
filter: {
|
|
60
|
+
extensions: [".md"]
|
|
61
|
+
},
|
|
62
|
+
read: {
|
|
63
|
+
path: (filePath, database, config) => undefined,
|
|
64
|
+
text: (text, database, config) => ({ metadata: { foo: "bar", bang: "baz" }, abstract: { baz: "bang" } }),
|
|
65
|
+
abstract: (abstract, database, config) => ({ abstract: { ...abstract, newAddition: true }, jobs: [testJob] }),
|
|
66
|
+
folder: (folder, database, config) => [testJob]
|
|
67
|
+
},
|
|
68
|
+
write: (destination, database, config) => {
|
|
69
|
+
return {
|
|
70
|
+
data: "yo there",
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** @type {VotivePlugin} */
|
|
76
|
+
const testPlugin = {
|
|
77
|
+
name: "test plugin",
|
|
78
|
+
runners: {
|
|
79
|
+
testRunner
|
|
80
|
+
},
|
|
81
|
+
router: (path) => path,
|
|
82
|
+
processors: [testProcessor]
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** @type {Runner} */
|
|
86
|
+
async function testRunner(data, database) {
|
|
87
|
+
const waiting = await new Promise((resolve) => setTimeout(() => resolve(data), 1000))
|
|
88
|
+
|
|
89
|
+
return waiting
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** @type {VotiveConfig} */
|
|
93
|
+
const config = {
|
|
94
|
+
sourceFolder: "./markdown",
|
|
95
|
+
destinationFolder: temp.path,
|
|
96
|
+
plugins: [testPlugin]
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** @type {FlatProcessors} */
|
|
100
|
+
const processors = [
|
|
101
|
+
{
|
|
102
|
+
plugin: testPlugin,
|
|
103
|
+
processor: testProcessor
|
|
104
|
+
}
|
|
105
|
+
]
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
const database = votive.createDatabase()
|
|
109
|
+
|
|
110
|
+
const moresources = database.getAllSources()
|
|
111
|
+
console.log({ moresources })
|
|
112
|
+
|
|
113
|
+
const stop = stopwatch("Read sources")
|
|
114
|
+
const { folders, sources } = await votive.readSources(config, database, processors)
|
|
115
|
+
stop()
|
|
116
|
+
|
|
117
|
+
const sourcesJobs = sources.flatMap(source => source.jobs)
|
|
118
|
+
const { processedAbstracts, abstractsJobs } = votive.readAbstracts(sources, config, database, processors)
|
|
119
|
+
|
|
120
|
+
const foldersJobs = readFolders(folders, config, database, processors)
|
|
121
|
+
|
|
122
|
+
database.createOrUpdateDestination({ metadata: { a: 1, b: 2 }, path: "abc.txt", abstract: { c: 3 }, syntax: "txt" })
|
|
123
|
+
const destination = database.getDestinationDependently("abc.txt", ["a"], "def")
|
|
124
|
+
database.createOrUpdateDestination({ metadata: { a: 3, b: 4 }, path: "def.txt", abstract: { c: 5 }, syntax: "txt" })
|
|
125
|
+
database.createOrUpdateDestination({ metadata: { a: 3, b: 9 }, path: "abc.txt", abstract: { c: 4 }, syntax: "txt" })
|
|
126
|
+
database.createOrUpdateDestination({ metadata: { a: 3, b: 9 }, path: "abc/def.txt", abstract: { c: 4 }, syntax: "txt" })
|
|
127
|
+
|
|
128
|
+
|
|
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
|
+
const oldSetting = database.setSetting("abc", "theme", "green")
|
|
133
|
+
|
|
134
|
+
const settings = database.getSettings("abc", "def")
|
|
135
|
+
|
|
136
|
+
const jobs = [...sourcesJobs, ...abstractsJobs, ...foldersJobs]
|
|
137
|
+
runJobs(jobs, config, database)
|
|
138
|
+
|
|
139
|
+
const dbResults = database.getAllSources()
|
|
140
|
+
|
|
141
|
+
const written = await votive.writeDestinations(config, database)
|
|
142
|
+
|
|
143
|
+
// const everything = database.getEverything()
|
|
144
|
+
// console.log(everything)
|
|
145
|
+
|
|
146
|
+
const newsources = database.getAllSources()
|
|
147
|
+
console.log({ newsources })
|
|
148
|
+
|
|
149
|
+
assert(true, "Read test tk")
|
|
150
|
+
temp.remove()
|
|
151
|
+
} catch (error) {
|
|
152
|
+
temp.remove()
|
|
153
|
+
throw error
|
|
154
|
+
}
|
|
155
|
+
})
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
console.log("tk")
|