Skip to content

Commit 94d7154

Browse files
committed
feat: add topo sort
1 parent fd41284 commit 94d7154

File tree

4 files changed

+208
-6
lines changed

4 files changed

+208
-6
lines changed

lib/index.js

+66-3
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@ const wordCountTransform = require('./transforms/wordCount')
88
const remoteTransform = require('./transforms/remote')
99
const { getSyntaxInfo } = require('./utils/syntax')
1010
const { onlyUnique, getCodeLocation, pluralize } = require('./utils')
11-
const { writeFile, resolveOutputPath, resolveFlatPath } = require('./utils/fs')
11+
const { readFile, writeFile, resolveOutputPath, resolveFlatPath } = require('./utils/fs')
1212
const { processFile } = require('./process-file')
1313
const { processContents } = require('./process-contents')
1414
const { parseMarkdown } = require('@davidwells/md-utils')
1515
const { success, error, info, convertHrtime, deepLog } = require('./utils/logs')
1616
const { OPEN_WORD, CLOSE_WORD, DEFAULT_GLOB_PATTERN } = require('./defaults')
17-
const { getBlockRegex } = require('./block-parser')
17+
const { getBlockRegex, parseBlocks } = require('./block-parser')
18+
const toposort = require('./utils/toposort');
1819
// const { hashFile } = require('./utils/hash-file')
1920
// const { getBlockRegex } = require('./block-parser')
2021
// const diff = require('../misc/old-test/utils/diff')
@@ -224,7 +225,69 @@ async function markdownMagic(globOrOpts = {}, options = {}) {
224225
process.exit(1)
225226
/** */
226227

227-
const processedFiles = files.map((file) => {
228+
229+
const blocksByPath = files.map(async (file) => {
230+
const text = await readFile(file, 'utf8')
231+
let foundBlocks = {}
232+
try {
233+
foundBlocks = parseBlocks(text, {
234+
syntax,
235+
open,
236+
close,
237+
})
238+
} catch (e) {
239+
throw new Error(`${e.message}\nFix content in ${file}\n`)
240+
}
241+
return {
242+
name: file,
243+
id: file,
244+
srcPath: file,
245+
blocks: foundBlocks.blocks
246+
}
247+
})
248+
249+
const blocks = (await Promise.all(blocksByPath)).map((item) => {
250+
const dir = path.dirname(item.srcPath)
251+
item.dependencies = []
252+
item.blocks.forEach((block) => {
253+
if (block.options && block.options.src) {
254+
const resolvedPath = path.resolve(dir, block.options.src)
255+
// if (resolvedPath.match(/\.md$/)) {
256+
// console.log('resolvedPath', resolvedPath)
257+
item.dependencies = item.dependencies.concat(resolvedPath)
258+
//}
259+
}
260+
})
261+
return item
262+
})
263+
/*
264+
console.log('blocks')
265+
deepLog(blocks)
266+
/** */
267+
268+
// Convert items into a format suitable for toposort
269+
const graph = blocks
270+
.filter((item) => item.blocks && item.blocks.length)
271+
.map((item) => {
272+
return [ item.id, ...item.dependencies ]
273+
})
274+
// console.log('graph', graph)
275+
// Perform the topological sort and reverse for execution order
276+
const sortedIds = toposort(graph).reverse();
277+
278+
// Reorder items based on sorted ids
279+
const sortedItems = sortedIds.map(id => blocks.find(item => item.id === id)).filter(Boolean);
280+
281+
// topoSort(blocks)
282+
const ordedFiles = sortedItems.map((item) => item.id)
283+
// console.log('sortedItems', sortedItems)
284+
// console.log('ordedFiles', ordedFiles)
285+
286+
/* @TODO
287+
288+
Blocks are already parsed, pass them into processFile to avoid reparse
289+
*/
290+
const processedFiles = ordedFiles.map((file) => {
228291
// logger('file', file)
229292
let newPath = path.resolve(cwd, file)
230293
/* Allow for different output directory */

lib/process-contents.js

+10-3
Original file line numberDiff line numberDiff line change
@@ -180,8 +180,12 @@ async function processContents(text, config) {
180180
const formattedNewContent = (options.noTrim) ? newContent : trimString(newContent)
181181
// console.log('formattedNewContent', formattedNewContent)
182182
/* Remove any conflicting imported comments */
183-
const fix = removeConflictingComments(formattedNewContent, foundBlocks.commentOpen, COMMENT_CLOSE_REGEX)
184-
// const fix = stripAllComments(formattedNewContent, foundBlocks.commentOpen, COMMENT_CLOSE_REGEX)
183+
const fix = removeConflictingComments(formattedNewContent, COMMENT_OPEN_REGEX, COMMENT_CLOSE_REGEX)
184+
// console.log('fix', fix)
185+
if (options.removeComments) {
186+
// console.log('removeComments', options.removeComments)
187+
}
188+
// const fix = stripAllComments(formattedNewContent, foundBlocks.COMMENT_OPEN_REGEX, COMMENT_CLOSE_REGEX)
185189

186190
// console.log('COMMENT_CLOSE_REGEX', COMMENT_CLOSE_REGEX)
187191
// console.log('formattedNewContent', formattedNewContent)
@@ -327,6 +331,8 @@ function getDetails({
327331
* @returns
328332
*/
329333
function removeConflictingComments(content, openPattern, closePattern) {
334+
// console.log('openPattern', openPattern)
335+
// console.log('closePattern', closePattern)
330336
const removeOpen = content.replace(openPattern, '')
331337
// TODO this probably needs to be a loop for larger blocks
332338
closePattern.lastIndex = 0; // reset regex
@@ -340,7 +346,8 @@ function getDetails({
340346
const closeTag = `${hasClose[2]}${hasClose[3] || ''}`
341347
// console.log('closeTag', closeTag)
342348
return removeOpen
343-
.replaceAll(closeTag, '')
349+
.replace(closePattern, '')
350+
// .replaceAll(closeTag, '')
344351
/* Trailing new line */
345352
.replace(/\n$/, '')
346353
}

lib/utils/toposort.js

+131
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
2+
// https://github.com/marcelklehr/toposort/tree/master
3+
/**
4+
* Topological sorting function
5+
*
6+
* @param {Array} edges
7+
* @returns {Array}
8+
*/
9+
10+
module.exports = function(edges) {
11+
return toposort(uniqueNodes(edges), edges)
12+
}
13+
14+
module.exports.array = toposort
15+
16+
function toposort(nodes, edges) {
17+
var cursor = nodes.length
18+
, sorted = new Array(cursor)
19+
, visited = {}
20+
, i = cursor
21+
// Better data structures make algorithm much faster.
22+
, outgoingEdges = makeOutgoingEdges(edges)
23+
, nodesHash = makeNodesHash(nodes)
24+
25+
// check for unknown nodes
26+
edges.forEach(function(edge) {
27+
if (!nodesHash.has(edge[0]) || !nodesHash.has(edge[1])) {
28+
throw new Error('Unknown node. There is an unknown node in the supplied edges.')
29+
}
30+
})
31+
32+
while (i--) {
33+
if (!visited[i]) visit(nodes[i], i, new Set())
34+
}
35+
36+
return sorted
37+
38+
function visit(node, i, predecessors) {
39+
if(predecessors.has(node)) {
40+
var nodeRep
41+
try {
42+
nodeRep = ", node was:" + JSON.stringify(node)
43+
} catch(e) {
44+
nodeRep = ""
45+
}
46+
throw new Error('Cyclic dependency' + nodeRep)
47+
}
48+
49+
if (!nodesHash.has(node)) {
50+
throw new Error('Found unknown node. Make sure to provided all involved nodes. Unknown node: '+JSON.stringify(node))
51+
}
52+
53+
if (visited[i]) return;
54+
visited[i] = true
55+
56+
var outgoing = outgoingEdges.get(node) || new Set()
57+
outgoing = Array.from(outgoing)
58+
59+
if (i = outgoing.length) {
60+
predecessors.add(node)
61+
do {
62+
var child = outgoing[--i]
63+
visit(child, nodesHash.get(child), predecessors)
64+
} while (i)
65+
predecessors.delete(node)
66+
}
67+
68+
sorted[--cursor] = node
69+
}
70+
}
71+
72+
function uniqueNodes(arr){
73+
var res = new Set()
74+
for (var i = 0, len = arr.length; i < len; i++) {
75+
var edge = arr[i]
76+
res.add(edge[0])
77+
res.add(edge[1])
78+
}
79+
return Array.from(res)
80+
}
81+
82+
function makeOutgoingEdges(arr){
83+
var edges = new Map()
84+
for (var i = 0, len = arr.length; i < len; i++) {
85+
var edge = arr[i]
86+
if (!edges.has(edge[0])) edges.set(edge[0], new Set())
87+
if (!edges.has(edge[1])) edges.set(edge[1], new Set())
88+
edges.get(edge[0]).add(edge[1])
89+
}
90+
return edges
91+
}
92+
93+
function makeNodesHash(arr){
94+
var res = new Map()
95+
for (var i = 0, len = arr.length; i < len; i++) {
96+
res.set(arr[i], i)
97+
}
98+
return res
99+
}
100+
101+
102+
function topoSortOld(arr) {
103+
var map = {} // Creates key value pair of name and object
104+
var result = [] // the result array
105+
var visited = {} // takes a note of the traversed dependency
106+
107+
arr.forEach(function(obj) { // build the map
108+
map[obj.name] = obj
109+
})
110+
111+
arr.forEach(function(obj) { // Traverse array
112+
if (!visited[obj.name]) { // check for visited object
113+
sort_util(obj)
114+
}
115+
})
116+
117+
// On visiting object, check for its dependencies and visit them recursively
118+
function sort_util(obj) {
119+
if (!obj) return;
120+
// console.log('obj', obj)
121+
visited[obj.name] = true
122+
obj.dependencies.forEach(function(dep){
123+
if (!visited[dep]) {
124+
sort_util(map[dep])
125+
}
126+
})
127+
result.push(obj)
128+
}
129+
130+
console.log(result);
131+
}

test/errors.test.js

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const SILENT = true
1414
test('Throw on unbalanced blocks', async () => {
1515
const fileName = 'error-unbalanced.md'
1616
const filePath = path.join(MARKDOWN_FIXTURE_DIR, fileName)
17+
// console.log('filePath', filePath)
1718
let error
1819
try {
1920
await markdownMagic(filePath, {

0 commit comments

Comments
 (0)