votive 0.3.2 → 0.4.1
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 +11 -4
- package/lib/bundle.js +5 -2
- package/lib/createDatabase.js +5 -3
- package/lib/readAbstracts.js +12 -9
- package/lib/readFolders.js +18 -15
- package/lib/readSources.js +172 -41
- package/lib/runJobs.js +26 -8
- package/lib/utils/index.js +13 -5
- package/lib/writeDestinations.js +0 -1
- package/package.json +2 -1
- package/tests/index.js +302 -245
- package/lib/cleanDeletions.js +0 -32
package/README.md
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
# Votive
|
|
2
2
|
|
|
3
|
+
*File processor*
|
|
4
|
+
|
|
5
|
+
- Powers [Voot](https://github.com/samlfair/voot)
|
|
6
|
+
- Bundles [Vowel](https://github.com/samlfair/vowel)
|
|
7
|
+
|
|
3
8
|
## Roadmap
|
|
4
9
|
|
|
5
|
-
- [ ] ReadPaths
|
|
6
10
|
- [ ] Flesh out job runners
|
|
7
|
-
- [ ]
|
|
11
|
+
- [ ] Rename jobs and paths
|
|
12
|
+
- [ ] Better dependency tracking
|
|
13
|
+
- [ ] Better query filters
|
|
14
|
+
- [x] File deletion handling
|
|
8
15
|
|
|
9
|
-
##
|
|
16
|
+
## Project: Jobs
|
|
10
17
|
|
|
11
|
-
|
|
18
|
+
Jobs was originally a generic concept, but after working it's clear that there are two main categories of jobs: async writes and data fetching. We can probably handle async writes with inbuilt logic. So, instead of a "job", we should have "read uri"? That way we can cache all the uris.
|
package/lib/bundle.js
CHANGED
|
@@ -10,6 +10,10 @@ import { styleText } from "node:util"
|
|
|
10
10
|
|
|
11
11
|
/** @import {Database} from "./createDatabase.js" */
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* @typedef {Database} Database
|
|
15
|
+
*/
|
|
16
|
+
|
|
13
17
|
/**
|
|
14
18
|
* @typedef {object} VotivePlugin
|
|
15
19
|
* @property {string} name
|
|
@@ -245,8 +249,6 @@ async function bundle(config, cache) {
|
|
|
245
249
|
// Write destination files
|
|
246
250
|
await writeDestinations(config, database)
|
|
247
251
|
|
|
248
|
-
const stale = database.target.getStale()
|
|
249
|
-
|
|
250
252
|
writeTime()
|
|
251
253
|
|
|
252
254
|
// database.commit()
|
|
@@ -282,6 +284,7 @@ async function bundler(config) {
|
|
|
282
284
|
queue.push(bundle(config))
|
|
283
285
|
cache = await queue[0]
|
|
284
286
|
} else {
|
|
287
|
+
"cache"
|
|
285
288
|
queue.push(bundle(config, cache))
|
|
286
289
|
await queue[0]
|
|
287
290
|
}
|
package/lib/createDatabase.js
CHANGED
|
@@ -659,7 +659,9 @@ function createDatabase(databasePath = ".votive.db") {
|
|
|
659
659
|
const type = typeof value
|
|
660
660
|
const safeValue = type === "object"
|
|
661
661
|
? JSON.stringify(value)
|
|
662
|
-
:
|
|
662
|
+
: type === "boolean"
|
|
663
|
+
? Number(value)
|
|
664
|
+
: value
|
|
663
665
|
|
|
664
666
|
const descendents = folder === ""
|
|
665
667
|
? "%"
|
|
@@ -746,13 +748,13 @@ function createDatabase(databasePath = ".votive.db") {
|
|
|
746
748
|
* @param {string} filePath
|
|
747
749
|
*/
|
|
748
750
|
delete(filePath) {
|
|
749
|
-
|
|
750
|
-
prepared.target.delete.get(filePath)
|
|
751
|
+
const deleted = prepared.target.delete.get(filePath)
|
|
751
752
|
prepared.metadata.deleteByTarget.all(filePath)
|
|
752
753
|
const deps = queries.dependency.getAllByTarget(filePath)
|
|
753
754
|
deps.forEach(dep => {
|
|
754
755
|
queries.target.markStale(dep.dependent)
|
|
755
756
|
})
|
|
757
|
+
return deleted
|
|
756
758
|
},
|
|
757
759
|
|
|
758
760
|
/**
|
package/lib/readAbstracts.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** @import {VotiveConfig, FlatProcessors, Abstract, AbstractsWithSyntax, Jobs, FlatProcessor, Abstracts} from "./bundle.js" */
|
|
2
2
|
/** @import {Database} from "./createDatabase.js" */
|
|
3
|
-
/** @import {
|
|
3
|
+
/** @import {ReadSourceFilesResult} from "./readSources.js"
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @typedef {object} ReadAbstractsResult
|
|
@@ -13,15 +13,17 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
|
-
* @param {
|
|
16
|
+
* @param {ReadSourceFilesResult} files
|
|
17
17
|
* @param {VotiveConfig} config
|
|
18
18
|
* @param {Database} database
|
|
19
19
|
* @param {FlatProcessors} processors
|
|
20
20
|
* @returns {{processedAbstracts: Abstracts, abstractsJobs: Jobs}}
|
|
21
21
|
*/
|
|
22
|
-
function readAbstracts(
|
|
22
|
+
function readAbstracts(files, config, database, processors) {
|
|
23
23
|
|
|
24
|
-
const processed =
|
|
24
|
+
const processed = files.flatMap(file => {
|
|
25
|
+
|
|
26
|
+
const { abstract: unprocessedAbstract } = file
|
|
25
27
|
|
|
26
28
|
/**
|
|
27
29
|
* @param {FlatProcessors} processors
|
|
@@ -32,13 +34,14 @@ function readAbstracts(abstracts, config, database, processors) {
|
|
|
32
34
|
const [flatProcessor, ...rest] = processors
|
|
33
35
|
if (!flatProcessor) return { abstract, jobs }
|
|
34
36
|
const { processor, plugin } = flatProcessor
|
|
35
|
-
if (processor.syntax
|
|
36
|
-
|| !processor.
|
|
37
|
-
|| !processor.read.abstract
|
|
37
|
+
if (!processor.extensions.includes(file.syntax)
|
|
38
|
+
|| !processor.transformFile
|
|
38
39
|
) return recursiveProcess(rest, abstract, jobs)
|
|
39
40
|
|
|
40
|
-
const
|
|
41
|
-
|
|
41
|
+
const settings = database.setting.getByFolder(file.dir)
|
|
42
|
+
|
|
43
|
+
const processed = processor.transformFile(abstract, database, config)
|
|
44
|
+
processed.jobs && processed.jobs.forEach(job => job.syntax = file.syntax)
|
|
42
45
|
return recursiveProcess(rest, processed.abstract, [...jobs, ...(processed.jobs || [])])
|
|
43
46
|
}
|
|
44
47
|
|
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
|
|
@@ -12,33 +13,35 @@ import path from "node:path"
|
|
|
12
13
|
*/
|
|
13
14
|
function readFolders(folders = [], config, database, processors) {
|
|
14
15
|
|
|
15
|
-
const folderProcessors = processors.filter(({ processor }) => processor.
|
|
16
|
+
const folderProcessors = processors.filter(({ processor }) => processor.readFolder)
|
|
16
17
|
|
|
17
18
|
const processed = folderProcessors.flatMap(({ processor, plugin }) => {
|
|
18
19
|
|
|
19
20
|
const jobs = folders.flatMap(folder => {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const {
|
|
24
|
-
jobs.forEach(job => job.
|
|
25
|
-
if (
|
|
26
|
-
|
|
27
|
-
database.
|
|
21
|
+
let folderPath = path.join(folder.parentPath, folder.name)
|
|
22
|
+
if(folderPath) folderPath += path.sep
|
|
23
|
+
|
|
24
|
+
const { targets, jobs } = processor.readFolder(folderPath, database, config)
|
|
25
|
+
jobs.forEach(job => job.syntax = processor.syntax)
|
|
26
|
+
if (targets) {
|
|
27
|
+
targets.forEach(target => {
|
|
28
|
+
database.target.create(target)
|
|
28
29
|
})
|
|
29
30
|
return jobs
|
|
30
31
|
}
|
|
31
32
|
})
|
|
32
33
|
|
|
33
|
-
|
|
34
|
+
|
|
35
|
+
const rootPath = path.relative(config.sourceFolder, "")
|
|
36
|
+
const rootFolder = processor.readFolder(rootPath, database, config, true)
|
|
34
37
|
|
|
35
|
-
if (rootFolder.
|
|
36
|
-
rootFolder.
|
|
37
|
-
database.
|
|
38
|
+
if (rootFolder.targets) {
|
|
39
|
+
rootFolder.targets.forEach(target => {
|
|
40
|
+
database.target.create(target)
|
|
38
41
|
})
|
|
39
42
|
}
|
|
40
43
|
|
|
41
|
-
rootFolder.jobs.forEach(job => job.
|
|
44
|
+
rootFolder.jobs.forEach(job => job.syntax = processor.syntax)
|
|
42
45
|
|
|
43
46
|
if(rootFolder.jobs) jobs.push(... rootFolder.jobs)
|
|
44
47
|
return jobs
|
package/lib/readSources.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { decodeBuffer } from "encoding-sniffer"
|
|
2
2
|
import fs from "node:fs/promises"
|
|
3
3
|
import path from "node:path"
|
|
4
|
-
import
|
|
4
|
+
import pLimit from "p-limit"
|
|
5
5
|
|
|
6
|
-
/** @import {VotiveConfig, VotivePlugin, VotiveProcessor, FlatProcessors, Abstracts, Abstract, Jobs, ProcessorSyntax} from "./bundle.js" */
|
|
6
|
+
/** @import {VotiveConfig, VotivePlugin, VotiveProcessor, FlatProcessors, Abstracts, Abstract, Jobs, ProcessorSyntax, Router} from "./bundle.js" */
|
|
7
7
|
/** @import {Dirent} from "node:fs" */
|
|
8
8
|
/** @import {Database} from "./createDatabase.js" */
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* @typedef {object} ReadSourcesResult
|
|
12
12
|
* @property {Dirent[]} folders
|
|
13
|
-
* @property {
|
|
13
|
+
* @property {ReadSourceFileResult[]} sources
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
/**
|
|
@@ -25,24 +25,56 @@ async function readSources(config, database, processors) {
|
|
|
25
25
|
recursive: true
|
|
26
26
|
})
|
|
27
27
|
|
|
28
|
-
const filteredDirents = (dirents || []).filter(fileFilter(config
|
|
28
|
+
const filteredDirents = (dirents || []).filter(fileFilter(config))
|
|
29
|
+
|
|
29
30
|
const { files, folders } = Object.groupBy(filteredDirents, (dirent) => dirent.isFile() ? "files" : "folders")
|
|
31
|
+
const loadingFiles = files.map(a => path.normalize(path.format({ name: a.name, dir: a.parentPath })))
|
|
30
32
|
if (!files) return { folders, sources: [] }
|
|
33
|
+
const limit = pLimit(5)
|
|
31
34
|
const readingSourceFiles = files.flatMap(readSourceFile(processors, database, config))
|
|
32
|
-
|
|
35
|
+
|
|
36
|
+
const reading = [
|
|
37
|
+
(await Promise.all(readingSourceFiles)).filter(a => a),
|
|
38
|
+
pruneDeletions(config, database, filteredDirents)
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
const [sources, deletedSources] = await Promise.all(reading)
|
|
42
|
+
|
|
33
43
|
return {
|
|
34
44
|
folders,
|
|
35
|
-
sources
|
|
45
|
+
sources: [...sources, ...deletedSources]
|
|
36
46
|
}
|
|
37
47
|
}
|
|
38
48
|
|
|
39
49
|
/**
|
|
40
50
|
* @param {VotiveConfig} config
|
|
41
51
|
* @param {Database} database
|
|
52
|
+
* @param {Dirent[]} dirents
|
|
42
53
|
*/
|
|
43
|
-
function
|
|
54
|
+
async function pruneDeletions(config, database, dirents) {
|
|
55
|
+
const sourceFilePaths = new Set(dirents.map(d => d.isFile() && path.join(d.parentPath, d.name)))
|
|
56
|
+
const sourceRecords = database.source.getAll()
|
|
57
|
+
const sourceRecordPaths = new Set(sourceRecords.map(r => r.path))
|
|
58
|
+
|
|
59
|
+
const deletions = sourceRecordPaths.difference(sourceFilePaths)
|
|
60
|
+
|
|
61
|
+
let deletedSources = []
|
|
62
|
+
|
|
63
|
+
deletions.forEach(deletion => {
|
|
64
|
+
deletedSources.push(database.source.delete(deletion))
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
return deletedSources.filter(a => a)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @param {VotiveConfig} config
|
|
72
|
+
*/
|
|
73
|
+
function fileFilter(config) {
|
|
44
74
|
/** @param {Dirent} dirent */
|
|
45
75
|
return (dirent) => {
|
|
76
|
+
const isDestinationFolder = !path.relative(config.destinationFolder, path.join(dirent.parentPath, dirent.name))
|
|
77
|
+
|
|
46
78
|
if (dirent.parentPath === config.destinationFolder) {
|
|
47
79
|
return false
|
|
48
80
|
} else if (dirent.parentPath.startsWith(config.destinationFolder + path.sep)) {
|
|
@@ -51,21 +83,28 @@ function fileFilter(config, database) {
|
|
|
51
83
|
return false // Ignore hidden files
|
|
52
84
|
} else if (dirent.parentPath.includes(path.sep + ".")) {
|
|
53
85
|
return false // Ignore hidden folders
|
|
86
|
+
} else if (dirent.parentPath.match(/^\.\w/)) {
|
|
87
|
+
return false // Ignore hidden folders
|
|
88
|
+
} if (isDestinationFolder) {
|
|
89
|
+
return false
|
|
54
90
|
}
|
|
55
91
|
return true
|
|
56
92
|
}
|
|
57
93
|
}
|
|
58
94
|
|
|
59
95
|
/**
|
|
60
|
-
* @typedef {
|
|
96
|
+
* @typedef {ReadSourceFileResult[]} ReadSourceFilesResult
|
|
61
97
|
*/
|
|
62
98
|
|
|
63
99
|
/**
|
|
64
|
-
* @typedef {object}
|
|
100
|
+
* @typedef {object} ReadSourceFileResult
|
|
65
101
|
* @property {Abstract} abstract
|
|
66
102
|
* @property {ProcessorSyntax} syntax
|
|
103
|
+
* @property {object} [metadata]
|
|
104
|
+
* @property {string} [targetFilePath]
|
|
105
|
+
* @property {string} [dir]
|
|
67
106
|
* @property {Jobs} jobs
|
|
68
|
-
* @property {string}
|
|
107
|
+
* @property {string} sourceFilePath
|
|
69
108
|
*/
|
|
70
109
|
|
|
71
110
|
/**
|
|
@@ -76,12 +115,12 @@ function fileFilter(config, database) {
|
|
|
76
115
|
function readSourceFile(processors, database, config) {
|
|
77
116
|
/**
|
|
78
117
|
* @param {import("node:fs").Dirent} dirent
|
|
79
|
-
* @returns {Promise<
|
|
118
|
+
* @returns {Promise<ReadSourceFileResult>[]}
|
|
80
119
|
*/
|
|
81
120
|
return (dirent) => {
|
|
82
121
|
const { name, parentPath } = dirent
|
|
83
|
-
const
|
|
84
|
-
const
|
|
122
|
+
const sourceFilePath = path.join(parentPath, name)
|
|
123
|
+
const sourceFileInfo = path.parse(sourceFilePath)
|
|
85
124
|
|
|
86
125
|
const processing = processors.flatMap(({ plugin, processor }) => process(plugin, processor, config))
|
|
87
126
|
|
|
@@ -91,43 +130,102 @@ function readSourceFile(processors, database, config) {
|
|
|
91
130
|
* @param {VotiveConfig} config
|
|
92
131
|
*/
|
|
93
132
|
async function process(plugin, processor, config) {
|
|
94
|
-
const { read, filter,
|
|
95
|
-
if (filter.
|
|
133
|
+
const { readFile: read, extensions: filter, format } = processor
|
|
134
|
+
if (filter.includes(sourceFileInfo.ext) && read) {
|
|
96
135
|
|
|
97
136
|
// Check modified time
|
|
98
|
-
const stat = await fs.stat(
|
|
99
|
-
const source = database.
|
|
100
|
-
|
|
137
|
+
const stat = await fs.stat(sourceFilePath)
|
|
138
|
+
const source = database.source.get(sourceFilePath)
|
|
139
|
+
const diff = source && source.lastModified - Number(Math.floor(stat.mtimeMs))
|
|
101
140
|
|
|
102
|
-
if (source &&
|
|
141
|
+
if (source && diff > -1) {
|
|
142
|
+
return null
|
|
143
|
+
}
|
|
103
144
|
|
|
104
|
-
database.
|
|
145
|
+
database.setting.deleteBySource(sourceFilePath)
|
|
105
146
|
|
|
106
|
-
|
|
107
|
-
const
|
|
147
|
+
const targetFilePath = route(sourceFilePath, plugin, config)
|
|
148
|
+
const targetFileExtension = path.extname(targetFilePath)
|
|
108
149
|
|
|
109
150
|
// Set URL if exists
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
151
|
+
const allJobs = []
|
|
152
|
+
|
|
153
|
+
if (format === "buffer") {
|
|
154
|
+
|
|
155
|
+
// FIXME update this type
|
|
156
|
+
const data = read(sourceFilePath, database, config)
|
|
157
|
+
const { metadata, abstract, jobs } = data
|
|
114
158
|
|
|
115
|
-
|
|
159
|
+
const target = database.target.create({
|
|
160
|
+
metadata,
|
|
161
|
+
abstract,
|
|
162
|
+
path: targetFilePath,
|
|
163
|
+
})
|
|
116
164
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
165
|
+
|
|
166
|
+
if (Array.isArray(jobs)) {
|
|
167
|
+
allJobs.push(...jobs)
|
|
168
|
+
}
|
|
121
169
|
}
|
|
122
170
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
171
|
+
if (format === "text") {
|
|
172
|
+
const stats = await fs.stat(sourceFilePath)
|
|
173
|
+
const data = await fs.readFile(sourceFilePath, { encoding: "utf-8" })
|
|
174
|
+
|
|
175
|
+
const content = read(data, sourceFilePath, targetFilePath, database, config)
|
|
176
|
+
if (content) {
|
|
177
|
+
|
|
178
|
+
const { jobs, abstract, metadata } = read(data, sourceFilePath, targetFilePath, database, config)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
if (Array.isArray(jobs)) {
|
|
182
|
+
allJobs.push(...jobs)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const target = database.target.create({
|
|
186
|
+
abstract,
|
|
187
|
+
path: targetFilePath,
|
|
188
|
+
metadata,
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
// FIXME this won't work with the refactor
|
|
192
|
+
allJobs && allJobs.forEach(job => job.syntax = processor.extensions[0])
|
|
193
|
+
updateSource()
|
|
194
|
+
return {
|
|
195
|
+
abstract,
|
|
196
|
+
metadata,
|
|
197
|
+
syntax: targetFileExtension,
|
|
198
|
+
targetFilePath: target.path,
|
|
199
|
+
dir: target.dir,
|
|
200
|
+
sourceFilePath,
|
|
201
|
+
jobs: allJobs
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// FIXME Job syntax
|
|
207
|
+
allJobs && allJobs.forEach(job => job.syntax = processor.extensions[0])
|
|
208
|
+
updateSource()
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
jobs: allJobs,
|
|
212
|
+
abstract: null,
|
|
213
|
+
targetFilePath: null,
|
|
214
|
+
sourceFilePath: null,
|
|
215
|
+
metadata: null,
|
|
216
|
+
syntax: null
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function updateSource() {
|
|
220
|
+
const timeStamp = stat.mtimeMs.toFixed()
|
|
221
|
+
|
|
222
|
+
if (source) {
|
|
223
|
+
database.source.updateTimestamp(sourceFilePath, Number(timeStamp))
|
|
224
|
+
} else {
|
|
225
|
+
database.source.create(sourceFilePath, targetFilePath, Number(timeStamp))
|
|
226
|
+
}
|
|
227
|
+
}
|
|
129
228
|
|
|
130
|
-
return { abstract, destinationPath, syntax, jobs }
|
|
131
229
|
}
|
|
132
230
|
}
|
|
133
231
|
|
|
@@ -138,10 +236,43 @@ function readSourceFile(processors, database, config) {
|
|
|
138
236
|
/**
|
|
139
237
|
* @param {string} filePath
|
|
140
238
|
* @param {VotivePlugin} plugin
|
|
239
|
+
* @param {VotiveConfig} config
|
|
141
240
|
*/
|
|
142
|
-
function route(filePath, plugin) {
|
|
143
|
-
|
|
144
|
-
|
|
241
|
+
function route(filePath, plugin, config) {
|
|
242
|
+
const { dir, ...parsedPath } = path.parse(filePath)
|
|
243
|
+
const rooty = !path.relative(config.sourceFolder, dir)
|
|
244
|
+
const segments = ["", ...dir.split(path.sep).filter(a => a)]
|
|
245
|
+
const pathInfo = {
|
|
246
|
+
inRootDir: rooty,
|
|
247
|
+
dir: segments,
|
|
248
|
+
...parsedPath
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (!plugin.router) return "0"
|
|
252
|
+
|
|
253
|
+
const routedPath = plugin.router(pathInfo)
|
|
254
|
+
|
|
255
|
+
if (!routedPath) return "0"
|
|
256
|
+
|
|
257
|
+
if (routedPath.hasOwnProperty("dir") && Array.isArray(routedPath.dir)) {
|
|
258
|
+
return path.normalize(path.format({
|
|
259
|
+
dir: path.join(...routedPath.dir),
|
|
260
|
+
root: routedPath.root || "",
|
|
261
|
+
base: routedPath.base,
|
|
262
|
+
name: routedPath.name,
|
|
263
|
+
ext: routedPath.ext
|
|
264
|
+
}))
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const routedInfo = {
|
|
268
|
+
dir: routedPath.dir || "",
|
|
269
|
+
root: routedPath.root || "",
|
|
270
|
+
base: routedPath.base,
|
|
271
|
+
name: routedPath.name,
|
|
272
|
+
ext: routedPath.ext
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return path.normalize(path.format(routedInfo))
|
|
145
276
|
}
|
|
146
277
|
|
|
147
278
|
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,35 @@ 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
|
+
if (response.status >= 200 && response.status < 300) {
|
|
20
|
+
const data = await response[job.runner]()
|
|
21
|
+
const processed = processor.fetcher(data)
|
|
22
|
+
database.createURL(job.data, processed, job.destination)
|
|
23
|
+
return
|
|
24
|
+
} else {
|
|
25
|
+
console.warn(`Error fetching URL: ${job.data}`)
|
|
26
|
+
}
|
|
27
|
+
} catch (e) {
|
|
28
|
+
console.error(e)
|
|
29
|
+
return
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
return Promise.allSettled(processing)
|
|
17
35
|
})
|
|
18
36
|
|
|
19
|
-
const
|
|
20
|
-
|
|
37
|
+
const settled = await Promise.allSettled(running)
|
|
38
|
+
return settled
|
|
21
39
|
}
|
|
22
40
|
|
|
23
41
|
export default runJobs
|
package/lib/utils/index.js
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import { styleText } from "node:util"
|
|
2
2
|
import { statSync } from "node:fs"
|
|
3
|
+
import path from "path"
|
|
3
4
|
|
|
4
|
-
/**
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* @param {string} label
|
|
7
|
+
* @param {string} message
|
|
8
|
+
* @param {boolean} verbose
|
|
9
|
+
*/
|
|
10
|
+
export function stopwatch(label, message, verbose) {
|
|
6
11
|
const start = performance.now()
|
|
7
12
|
|
|
8
13
|
function stop() {
|
|
9
14
|
const end = performance.now()
|
|
10
15
|
const duration = end - start
|
|
11
|
-
console.info(`${label}
|
|
16
|
+
if(verbose) console.info(`${styleText("dim", label + ":")} ${styleText("magenta", message)} ${styleText("magenta", duration.toFixed(2) + "ms")}`)
|
|
12
17
|
}
|
|
13
18
|
|
|
14
19
|
return stop
|
|
@@ -18,8 +23,11 @@ export function stopwatch(label) {
|
|
|
18
23
|
* @param {string} urlPath
|
|
19
24
|
*/
|
|
20
25
|
export function splitURL(urlPath) {
|
|
21
|
-
const [urlFileName, ...urlDirSegmentsReversed] = urlPath.split("/").reverse()
|
|
22
|
-
|
|
26
|
+
const [urlFileName, ...urlDirSegmentsReversed] = urlPath.split("/").filter(a => a).reverse()
|
|
27
|
+
const segments = urlDirSegmentsReversed.reverse()
|
|
28
|
+
segments.push('')
|
|
29
|
+
const folder = path.relative("", segments.join(path.sep))
|
|
30
|
+
return folder
|
|
23
31
|
}
|
|
24
32
|
|
|
25
33
|
/** @param {string} dbPath */
|
package/lib/writeDestinations.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "votive",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "A file processor.",
|
|
5
5
|
"homepage": "https://github.com/samlfair/votive#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"encoding-sniffer": "^0.2.1",
|
|
27
|
+
"p-limit": "^7.3.0",
|
|
27
28
|
"unified": "^11.0.5",
|
|
28
29
|
"unist-util-visit": "^5.0.0",
|
|
29
30
|
"workerpool": "^10.0.1"
|
package/tests/index.js
CHANGED
|
@@ -13,21 +13,84 @@ import { checkFile } from "./../lib/utils/index.js"
|
|
|
13
13
|
|
|
14
14
|
process.chdir("./tests")
|
|
15
15
|
|
|
16
|
-
test("
|
|
17
|
-
const
|
|
16
|
+
test("database creation in memory", (t) => {
|
|
17
|
+
const database = votive.createDatabase(":memory:")
|
|
18
|
+
|
|
19
|
+
t.test("database functions", () => {
|
|
20
|
+
const databaseKeys = new Set(Object.keys(database))
|
|
21
|
+
const expectedKeys = new Set(["saveDB", "source", "setting", "dependency", "target", "url"])
|
|
22
|
+
const difference = databaseKeys.difference(expectedKeys)
|
|
23
|
+
assert(difference.size === 0)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
t.test("create sources", () => {
|
|
27
|
+
database.source.create("abc.in", "abc.out", 1)
|
|
28
|
+
database.source.create("def.in", "def.out", 2)
|
|
29
|
+
const sources = database.source.getAll()
|
|
30
|
+
assert(sources.length === 2)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
t.test("create targets", () => {
|
|
34
|
+
database.target.create({
|
|
35
|
+
path: "abc.out",
|
|
36
|
+
abstract: ["abc"],
|
|
37
|
+
metadata: {
|
|
38
|
+
color: "blue",
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
database.target.create({
|
|
43
|
+
path: "def.out",
|
|
44
|
+
abstract: ["def"],
|
|
45
|
+
metadata: {
|
|
46
|
+
color: "red",
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
const targets = database.target.getAll()
|
|
51
|
+
|
|
52
|
+
assert(targets.length === 2)
|
|
53
|
+
})
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
test("bundle", async () => {
|
|
57
|
+
const tempDest = fs.mkdtempDisposableSync("target-")
|
|
18
58
|
const tempSource = fs.mkdtempDisposableSync("source-")
|
|
19
59
|
|
|
60
|
+
fs.writeFileSync(
|
|
61
|
+
path.join(tempSource.path, "abc.in"),
|
|
62
|
+
JSON.stringify({ abstract: ["abc"], metadata: { color: "blue" } }),
|
|
63
|
+
"utf-8"
|
|
64
|
+
)
|
|
65
|
+
|
|
20
66
|
try {
|
|
21
|
-
const stop = stopwatch("Bundle
|
|
22
|
-
await votive.bundle({
|
|
67
|
+
const stop = stopwatch("Bundle folder")
|
|
68
|
+
const database = await votive.bundle({
|
|
23
69
|
sourceFolder: tempSource.path,
|
|
24
70
|
destinationFolder: tempDest.path,
|
|
25
|
-
plugins: [
|
|
71
|
+
plugins: [{
|
|
72
|
+
name: "test-plugin",
|
|
73
|
+
processors: [{
|
|
74
|
+
format: "text",
|
|
75
|
+
extensions: [".in"],
|
|
76
|
+
// readResource: null,
|
|
77
|
+
writeFile: (destination) => ({ data: destination.abstract[0] }),
|
|
78
|
+
readFile: (data) => ({ abstract: [data], metadata: { color: "green" } }),
|
|
79
|
+
// readFolder: null,
|
|
80
|
+
// transformFile: null
|
|
81
|
+
}],
|
|
82
|
+
router: ({ base, dir, root }) => ({ base, root, ext: ".out" }),
|
|
83
|
+
}]
|
|
84
|
+
|
|
26
85
|
})
|
|
27
86
|
|
|
87
|
+
const sources = database.source.getAll()
|
|
88
|
+
|
|
89
|
+
const targets = database.target.getAll()
|
|
90
|
+
|
|
28
91
|
stop()
|
|
29
92
|
|
|
30
|
-
const dir = fs.readdirSync(tempDest.path)
|
|
93
|
+
const dir = fs.readdirSync(tempDest.path, { recursive: true })
|
|
31
94
|
assert(dir.length === 0, "Destination directory is not empty.")
|
|
32
95
|
} catch (error) {
|
|
33
96
|
tempDest.remove()
|
|
@@ -40,281 +103,275 @@ test("empty directory", async () => {
|
|
|
40
103
|
tempSource.remove()
|
|
41
104
|
})
|
|
42
105
|
|
|
43
|
-
test("internals", async (t) => {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
106
|
+
// test("internals", async (t) => {
|
|
107
|
+
// const dbExists = checkFile(".votive.db")
|
|
108
|
+
// if (dbExists) await rm(".votive.db")
|
|
109
|
+
|
|
110
|
+
// const temp = fs.mkdtempDisposableSync("destination-")
|
|
111
|
+
|
|
112
|
+
// /** @returns {Job} */
|
|
113
|
+
// function createExampleJob() {
|
|
114
|
+
// return {
|
|
115
|
+
// data: Math.floor(Math.random() * 1000),
|
|
116
|
+
// runner: "exampleRunner"
|
|
117
|
+
// }
|
|
118
|
+
// }
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
// /** @type {ReadText} */
|
|
122
|
+
// function exampleTextReader(text, filePath, destinationPath, database, config) {
|
|
123
|
+
// const matches = text.match(/\b\w+\b/)
|
|
124
|
+
// const title = matches ? matches[0] : "Untitled"
|
|
125
|
+
|
|
126
|
+
// /** @type {ReadTextResult} */
|
|
127
|
+
// return {
|
|
128
|
+
// abstract: {
|
|
129
|
+
// content: text,
|
|
130
|
+
// },
|
|
131
|
+
// metadata: {
|
|
132
|
+
// title
|
|
133
|
+
// },
|
|
134
|
+
// jobs: [
|
|
135
|
+
// createExampleJob()
|
|
136
|
+
// ]
|
|
137
|
+
// }
|
|
138
|
+
// }
|
|
139
|
+
|
|
140
|
+
// /** @type {ReadAbstract} */
|
|
141
|
+
// function exampleAbstractReader(abstract, database, config) {
|
|
142
|
+
// abstract.exampleAppend = true
|
|
143
|
+
// return { abstract, jobs: [createExampleJob()] }
|
|
144
|
+
// }
|
|
145
|
+
|
|
146
|
+
// /** @type {ReadFolder} */
|
|
147
|
+
// function exampleFolderReader(folder, database, config) {
|
|
148
|
+
// return {
|
|
149
|
+
// jobs: [createExampleJob()],
|
|
150
|
+
// destinations: [
|
|
151
|
+
// {
|
|
152
|
+
// path: "index.html",
|
|
153
|
+
// abstract: { content: "" },
|
|
154
|
+
// metadata: {
|
|
155
|
+
// title: "home"
|
|
156
|
+
// },
|
|
157
|
+
// syntax: "md"
|
|
158
|
+
// }
|
|
159
|
+
// ]
|
|
160
|
+
// }
|
|
161
|
+
// }
|
|
162
|
+
|
|
163
|
+
// /** @type {VotiveProcessor} */
|
|
164
|
+
// const exampleProcessor = {
|
|
165
|
+
// syntax: "md",
|
|
166
|
+
// filter: {
|
|
167
|
+
// extensions: [".md"]
|
|
168
|
+
// },
|
|
169
|
+
// read: {
|
|
170
|
+
// text: exampleTextReader,
|
|
171
|
+
// abstract: exampleAbstractReader,
|
|
172
|
+
// folder: exampleFolderReader
|
|
173
|
+
// },
|
|
174
|
+
// write: (destination, database, config) => {
|
|
175
|
+
// return {
|
|
176
|
+
// data: "lorem ipsum",
|
|
177
|
+
// }
|
|
178
|
+
// }
|
|
179
|
+
// }
|
|
180
|
+
|
|
181
|
+
// /** @param {string} sourcePath */
|
|
182
|
+
// function router({ base, ...parsed }) {
|
|
183
|
+
// return { ...parsed, ext: ".html" }
|
|
184
|
+
// }
|
|
185
|
+
|
|
186
|
+
// /** @type {VotivePlugin} */
|
|
187
|
+
// const examplePlugin = {
|
|
188
|
+
// name: "example plugin",
|
|
189
|
+
// runners: {
|
|
190
|
+
// exampleRunner: exampleRunner
|
|
191
|
+
// },
|
|
192
|
+
// router,
|
|
193
|
+
// processors: [exampleProcessor]
|
|
194
|
+
// }
|
|
195
|
+
|
|
196
|
+
// /** @type {Runner} */
|
|
197
|
+
// async function exampleRunner(data, database) {
|
|
198
|
+
// return await new Promise((resolve) => setTimeout(() => resolve(data), 1))
|
|
199
|
+
// }
|
|
200
|
+
|
|
201
|
+
// /** @type {VotiveConfig} */
|
|
202
|
+
// const config = {
|
|
203
|
+
// sourceFolder: "./markdown",
|
|
204
|
+
// destinationFolder: temp.path,
|
|
205
|
+
// plugins: [examplePlugin]
|
|
206
|
+
// }
|
|
207
|
+
|
|
208
|
+
// /** @type {FlatProcessors} */
|
|
209
|
+
// const processors = [
|
|
210
|
+
// {
|
|
211
|
+
// plugin: examplePlugin,
|
|
212
|
+
// processor: exampleProcessor
|
|
213
|
+
// }
|
|
214
|
+
// ]
|
|
215
|
+
|
|
216
|
+
// try {
|
|
217
|
+
// fs.writeFileSync("markdown/prunee.md", "A little content")
|
|
218
|
+
|
|
219
|
+
// const database = votive.createDatabase()
|
|
220
|
+
// const sourcesOne = database.getAllSources()
|
|
221
|
+
|
|
222
|
+
// t.test("no sources on startup", () => {
|
|
223
|
+
// const isArray = Array.isArray(sourcesOne)
|
|
224
|
+
// const isEmpty = !sourcesOne.length
|
|
225
|
+
// assert(isArray && isEmpty)
|
|
226
|
+
// })
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
// const stop = stopwatch("Read sources")
|
|
231
|
+
|
|
232
|
+
// const { folders, sources } = await votive.readSources(config, database, processors)
|
|
233
|
+
|
|
234
|
+
// t.test('one folder exists', () => {
|
|
235
|
+
// assert(folders.length === 1)
|
|
236
|
+
// })
|
|
237
|
+
|
|
238
|
+
// t.test('three sources exist', () => {
|
|
239
|
+
// assert(sources.length === 4)
|
|
240
|
+
// })
|
|
241
|
+
|
|
242
|
+
// stop()
|
|
243
|
+
|
|
244
|
+
// const sourcesJobs = sources.flatMap(source => source.jobs)
|
|
145
245
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
}
|
|
152
|
-
]
|
|
246
|
+
// const { processedAbstracts, abstractsJobs } = votive.readAbstracts(sources, config, database, processors)
|
|
247
|
+
|
|
248
|
+
// t.test('transformation succeeded', () => {
|
|
249
|
+
// assert(processedAbstracts.find(abstract => abstract.exampleAppend))
|
|
250
|
+
// })
|
|
153
251
|
|
|
154
|
-
|
|
155
|
-
|
|
252
|
+
// t.test('abstract jobs exist', () => {
|
|
253
|
+
// assert(abstractsJobs.length > 0)
|
|
254
|
+
// assert(abstractsJobs.every(job => job.data && job.runner))
|
|
255
|
+
// })
|
|
156
256
|
|
|
157
|
-
|
|
158
|
-
const sourcesOne = database.getAllSources()
|
|
257
|
+
// const foldersJobs = readFolders(folders, config, database, processors)
|
|
159
258
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
})
|
|
259
|
+
// t.test("folders jobs exist", () => {
|
|
260
|
+
// assert(foldersJobs.length > 0)
|
|
261
|
+
// assert(foldersJobs.every(job => job.data && job.runner))
|
|
262
|
+
// })
|
|
165
263
|
|
|
264
|
+
// database.createOrUpdateDestination({ metadata: { a: 1, b: 2 }, path: "abc.html", abstract: { c: 3 }, syntax: "md" })
|
|
265
|
+
// const firstDestination = database.getDestinationIndependently("abc.html", [])
|
|
166
266
|
|
|
267
|
+
// t.test('first destination created', () => {
|
|
268
|
+
// assert(firstDestination.path === 'abc.html')
|
|
269
|
+
// })
|
|
167
270
|
|
|
168
|
-
|
|
271
|
+
// database.createOrUpdateDestination({ metadata: { a: 3, b: 4 }, path: "abc/def.html", abstract: { c: 5 }, syntax: "md" })
|
|
169
272
|
|
|
170
|
-
|
|
273
|
+
// /* TODO: Test that dependencies are working */
|
|
171
274
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
275
|
+
// const setting = database.setSetting("abc.html", "theme", "blue", "markdown/prunee.md")
|
|
276
|
+
// const newSetting = database.setSetting("abc", "category", "Dog", "markdown/prunee.md")
|
|
277
|
+
// const sameSetting = database.setSetting("abc", "category", "Dog", "markdown/prunee.md")
|
|
278
|
+
// const folderSetting = database.setSetting("abc", "theme", "red", "markdown/prunee.md")
|
|
175
279
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
280
|
+
// t.test('settings created', () => {
|
|
281
|
+
// assert(setting.value === 'blue')
|
|
282
|
+
// assert(newSetting.value === 'Dog')
|
|
283
|
+
// assert(!sameSetting)
|
|
284
|
+
// })
|
|
179
285
|
|
|
180
|
-
|
|
286
|
+
// const abcSettings = database.getSettings("abc.html")
|
|
181
287
|
|
|
182
|
-
|
|
288
|
+
// t.test('settings retrieved', () => {
|
|
289
|
+
// assert(abcSettings.theme[0] === "blue")
|
|
290
|
+
// })
|
|
183
291
|
|
|
184
|
-
|
|
292
|
+
// const descendentSetting = database.setSetting("abc/def.html", "theme", "green", "abc.md")
|
|
293
|
+
// const defSettings = database.getSettings("abc/def.html")
|
|
185
294
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
295
|
+
// t.test('settings retrieved', () => {
|
|
296
|
+
// assert(abcSettings.theme[0] === "blue")
|
|
297
|
+
// assert(defSettings.theme[1] === "green")
|
|
298
|
+
// })
|
|
189
299
|
|
|
190
|
-
|
|
191
|
-
assert(abstractsJobs.length > 0)
|
|
192
|
-
assert(abstractsJobs.every(job => job.data && job.runner))
|
|
193
|
-
})
|
|
300
|
+
// const staleDestinations = database.getStaleDestinations()
|
|
194
301
|
|
|
195
|
-
|
|
302
|
+
// t.test('all destinations are stale', () => {
|
|
303
|
+
// assert(staleDestinations.length === 7)
|
|
304
|
+
// })
|
|
196
305
|
|
|
197
|
-
|
|
198
|
-
assert(foldersJobs.length > 0)
|
|
199
|
-
assert(foldersJobs.every(job => job.data && job.runner))
|
|
200
|
-
})
|
|
306
|
+
// const written = await votive.writeDestinations(config, database)
|
|
201
307
|
|
|
202
|
-
|
|
203
|
-
const firstDestination = database.getDestinationIndependently("abc.html", [])
|
|
308
|
+
// const staleDestinationsAfterWriting = database.getStaleDestinations()
|
|
204
309
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
310
|
+
// t.test('all destinations are fresh', () => {
|
|
311
|
+
// assert(staleDestinationsAfterWriting.length === 0)
|
|
312
|
+
// })
|
|
208
313
|
|
|
209
|
-
|
|
210
|
-
|
|
314
|
+
// const updated = database.createOrUpdateDestination({ metadata: { a: 1, b: 9 }, path: "abc.html", abstract: { c: 4 }, syntax: "md" })
|
|
315
|
+
// const staleAfterAbstractUpdate = database.getStaleDestinations()
|
|
211
316
|
|
|
212
|
-
|
|
317
|
+
// t.test('updated document with no side effects is stale', () => {
|
|
318
|
+
// assert(staleAfterAbstractUpdate.length === 1)
|
|
319
|
+
// })
|
|
213
320
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
})
|
|
321
|
+
// await votive.writeDestinations(config, database)
|
|
322
|
+
// const staleAfterSecondWrite = database.getStaleDestinations()
|
|
217
323
|
|
|
324
|
+
// t.test('everything fresh again', () => {
|
|
325
|
+
// assert(staleAfterSecondWrite.length === 0)
|
|
326
|
+
// })
|
|
218
327
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
const sameSetting = database.setSetting("abc", "category", "Dog", "markdown/prunee.md")
|
|
222
|
-
const folderSetting = database.setSetting("abc", "theme", "red", "markdown/prunee.md")
|
|
328
|
+
// const updatedWithSideEffects = database.createOrUpdateDestination({ metadata: { a: 4, b: 9 }, path: "abc.html", abstract: { c: 4 }, syntax: "md" })
|
|
329
|
+
// const staleAfterSideEffects = database.getStaleDestinations()
|
|
223
330
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
assert(!sameSetting)
|
|
228
|
-
})
|
|
331
|
+
// t.test('side effects work', () => {
|
|
332
|
+
// assert(staleAfterSideEffects.length === 2)
|
|
333
|
+
// })
|
|
229
334
|
|
|
230
|
-
const abcSettings = database.getSettings("abc.html")
|
|
231
335
|
|
|
232
|
-
|
|
233
|
-
assert(abcSettings.theme[0] === "blue")
|
|
234
|
-
})
|
|
336
|
+
// await votive.writeDestinations(config, database)
|
|
235
337
|
|
|
236
|
-
|
|
237
|
-
const defSettings = database.getSettings("abc/def.html")
|
|
338
|
+
// const oldSetting = database.setSetting("abc", "theme", "green")
|
|
238
339
|
|
|
239
|
-
|
|
240
|
-
assert(abcSettings.theme[0] === "blue")
|
|
241
|
-
assert(defSettings.theme[1] === "green")
|
|
242
|
-
})
|
|
340
|
+
// const staleAfterSettingsChange = database.getStaleDestinations()
|
|
243
341
|
|
|
244
|
-
|
|
342
|
+
// t.test('setting affects descendents', () => {
|
|
343
|
+
// assert(staleAfterSettingsChange.length === 1)
|
|
344
|
+
// })
|
|
245
345
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
346
|
+
// // TODO: Write tests for jobs
|
|
347
|
+
// const jobs = [...sourcesJobs, ...abstractsJobs, ...foldersJobs]
|
|
348
|
+
// runJobs(jobs, config, database)
|
|
249
349
|
|
|
250
|
-
|
|
350
|
+
// fs.rmSync("markdown/prunee.md")
|
|
251
351
|
|
|
252
|
-
|
|
352
|
+
// await votive.pruneSources(config, database)
|
|
253
353
|
|
|
254
|
-
|
|
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()
|
|
354
|
+
// const result = database.getDestinationIndependently("markdown/prunee.html")
|
|
260
355
|
|
|
261
|
-
|
|
262
|
-
assert(staleAfterAbstractUpdate.length === 1)
|
|
263
|
-
})
|
|
356
|
+
// /* TODO: Check pruning metadata works (I removed it) */
|
|
264
357
|
|
|
265
|
-
|
|
266
|
-
|
|
358
|
+
// const finalDestinations = database.getDestinations({
|
|
359
|
+
// filter: [
|
|
360
|
+
// {
|
|
361
|
+
// property: "a",
|
|
362
|
+
// operator: "gt",
|
|
363
|
+
// value: 3
|
|
364
|
+
// }
|
|
365
|
+
// ]
|
|
366
|
+
// }, "markdown/prunee.html")
|
|
267
367
|
|
|
268
|
-
|
|
269
|
-
assert(staleAfterSecondWrite.length === 0)
|
|
270
|
-
})
|
|
368
|
+
// // TODO: Write a test for get destinations
|
|
271
369
|
|
|
272
|
-
|
|
273
|
-
const staleAfterSideEffects = database.getStaleDestinations()
|
|
370
|
+
// await database.saveDB()
|
|
274
371
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
await votive.writeDestinations(config, database)
|
|
282
|
-
|
|
283
|
-
const oldSetting = database.setSetting("abc", "theme", "green")
|
|
284
|
-
|
|
285
|
-
const staleAfterSettingsChange = database.getStaleDestinations()
|
|
286
|
-
|
|
287
|
-
t.test('setting affects descendents', () => {
|
|
288
|
-
assert(staleAfterSettingsChange.length === 1)
|
|
289
|
-
})
|
|
290
|
-
|
|
291
|
-
// TODO: Write tests for jobs
|
|
292
|
-
const jobs = [...sourcesJobs, ...abstractsJobs, ...foldersJobs]
|
|
293
|
-
runJobs(jobs, config, database)
|
|
294
|
-
|
|
295
|
-
const destinations = database.getAllDestinations()
|
|
296
|
-
database.getDestinationDependently("markdown/prunee.html", ["title"], "abc/def.html")
|
|
297
|
-
const newDependencies = database.getDependencies()
|
|
298
|
-
|
|
299
|
-
fs.rmSync("markdown/prunee.md")
|
|
300
|
-
|
|
301
|
-
await votive.pruneSources(config, database)
|
|
302
|
-
|
|
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
|
-
})
|
|
312
|
-
|
|
313
|
-
await database.saveDB()
|
|
314
|
-
|
|
315
|
-
temp.remove()
|
|
316
|
-
} catch (error) {
|
|
317
|
-
temp.remove()
|
|
318
|
-
throw error
|
|
319
|
-
}
|
|
320
|
-
})
|
|
372
|
+
// temp.remove()
|
|
373
|
+
// } catch (error) {
|
|
374
|
+
// temp.remove()
|
|
375
|
+
// throw error
|
|
376
|
+
// }
|
|
377
|
+
// })
|
package/lib/cleanDeletions.js
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { readdir, rm } 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 cleanDeletions(config, database) {
|
|
13
|
-
// Must remove from sources first??
|
|
14
|
-
const dirents = new Set((await readdir(config.destinationFolder, {
|
|
15
|
-
recursive: true,
|
|
16
|
-
withFileTypes: true
|
|
17
|
-
})).map(d => d.isFile() && path.relative(config.destinationFolder, path.join(d.parentPath, d.name)))
|
|
18
|
-
.filter(a => a)
|
|
19
|
-
)
|
|
20
|
-
|
|
21
|
-
const targets = new Set(database.target.getAll().map(t => t.path))
|
|
22
|
-
|
|
23
|
-
const deletions = dirents.difference(targets)
|
|
24
|
-
|
|
25
|
-
deletions.forEach(async deletion => {
|
|
26
|
-
const deletionPath = path.join(config.destinationFolder, deletion)
|
|
27
|
-
await rm(deletionPath)
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export default cleanDeletions
|