-
Notifications
You must be signed in to change notification settings - Fork 1
/
block-finder.js
140 lines (128 loc) · 3.38 KB
/
block-finder.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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
// native modules
const fs = require('fs');
const path = require('path');
// external modules
const fg = require('fast-glob');
/**
* Determine if content includes pattern (text or regexp)
*
* @param {String} content
* @param {String|RegExp} pattern
* @return {Boolean}
*/
function includes(content, pattern) {
if (pattern instanceof RegExp) {
return pattern.test(content);
}
return content.includes(pattern);
}
/**
* Use globs to find files and regex tokens to find text block matches inside those files
*
* @class BlockFinder
*/
class BlockFinder {
/**
* Instantiate BlockFinder
*
* @param {Object} props
* @return {BlockFinder} instance
*/
constructor(props) {
// required
this.glob = props.glob;
this.start = props.start;
this.stop = props.stop;
// optional
this.cwd = path.resolve(props.cwd || process.cwd());
this.ignore = props.ignore || '**/node_modules';
}
/**
* Convert relative file paths to absolute file paths
*
* @param {Array} relatives
* @return {Array} absolutes
*/
toAbsolutes(relatives) {
return relatives.map((relative) => path.resolve(`${this.cwd}/${relative}`));
}
/**
* Extract matching text block matches from text content
*
* @param {String} content
* @return {Promise.<Array>} blocks
*/
extractMatches(content) {
const matches = [];
let index = -1;
let recording = false;
content.split(/\n/).forEach((line) => {
if (includes(line, this.start)) {
index += 1;
recording = true;
matches[index] = '';
} else if (includes(line, this.stop)) {
recording = false;
} else if (recording) {
matches[index] += matches[index] ? `\n${line}` : line || '';
}
});
return matches;
}
/**
* Convert absolute file paths to file content objects
*
* @param {Array} absolutes
* @return {Promise.<Array>} objects
*/
async toObjects(absolutes) {
return Promise.all(absolutes.map(async (absolute) => {
const content = await fs.promises.readFile(absolute, 'utf8');
const matches = includes(content, this.start) && includes(content, this.stop);
return {
content,
matches: matches ? this.extractMatches(content) : [],
path: path.parse(absolute),
};
}));
}
/**
* Convert absolute file paths to file content objects synchronously
*
* @param {Array} absolutes
* @return {Array} objects
*/
toObjectsSync(absolutes) {
return absolutes.map((absolute) => {
const content = fs.readFileSync(absolute, 'utf8');
const matches = includes(content, this.start) && includes(content, this.stop);
return {
content,
matches: matches ? this.extractMatches(content) : [],
path: path.parse(absolute),
};
});
}
/**
* Find text blocks
*
* @return {Promise.<Array>} blocks
*/
async find() {
return fg(this.glob, { cwd: this.cwd, ignore: this.ignore })
.then((relatives) => this.toAbsolutes(relatives))
.then((absolutes) => this.toObjects(absolutes));
}
/**
* Find text blocks synchronously
*
* @return {Array} blocks
*/
findSync() {
const relatives = fg.sync(this.glob, { cwd: this.cwd, ignore: this.ignore });
const absolutes = this.toAbsolutes(relatives);
const objects = this.toObjectsSync(absolutes);
return objects;
}
}
module.exports = BlockFinder;