linguist-js 2.4.1 → 2.5.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/dist/cli.js +1 -0
- package/dist/helpers/load-data.js +11 -2
- package/dist/helpers/walk-tree.js +10 -5
- package/dist/index.js +54 -34
- package/dist/types.d.ts +1 -0
- package/ext/documentation.yml +17 -0
- package/ext/generated.rb +351 -0
- package/ext/heuristics.yml +690 -0
- package/ext/languages.yml +7633 -0
- package/ext/vendor.yml +166 -0
- package/package.json +11 -9
- package/readme.md +10 -7
package/dist/cli.js
CHANGED
|
@@ -19,6 +19,7 @@ commander_1.program
|
|
|
19
19
|
.option('-j|--json [bool]', 'Display the output as JSON', false)
|
|
20
20
|
.option('-t|--tree <traversal>', 'Which part of the output JSON to display (dot-delimited)')
|
|
21
21
|
.option('-q|--quick [bool]', 'Skip complex language analysis (alias for -{A|I|H|S}=false)', false)
|
|
22
|
+
.option('-o|--offline [bool]', 'Use packaged data files instead of fetching latest from GitHub', false)
|
|
22
23
|
.option('-V|--keepVendored [bool]', 'Prevent skipping over vendored/generated files', false)
|
|
23
24
|
.option('-B|--keepBinary [bool]', 'Prevent skipping over binary files', false)
|
|
24
25
|
.option('-r|--relativePaths [bool]', 'Convert absolute file paths to relative', false)
|
|
@@ -3,11 +3,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const fs_1 = __importDefault(require("fs"));
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
6
8
|
const cross_fetch_1 = __importDefault(require("cross-fetch"));
|
|
7
9
|
const node_cache_1 = __importDefault(require("node-cache"));
|
|
8
10
|
const cache = new node_cache_1.default({});
|
|
9
|
-
|
|
10
|
-
async function loadFile(file) {
|
|
11
|
+
async function loadWebFile(file) {
|
|
11
12
|
// Return cache if it exists
|
|
12
13
|
const cachedContent = cache.get(file);
|
|
13
14
|
if (cachedContent)
|
|
@@ -18,4 +19,12 @@ async function loadFile(file) {
|
|
|
18
19
|
cache.set(file, fileContent);
|
|
19
20
|
return fileContent;
|
|
20
21
|
}
|
|
22
|
+
async function loadLocalFile(file) {
|
|
23
|
+
const filePath = path_1.default.resolve(__dirname, '../../ext', file);
|
|
24
|
+
return fs_1.default.promises.readFile(filePath).then(buffer => buffer.toString());
|
|
25
|
+
}
|
|
26
|
+
/** Load a data file from github-linguist. */
|
|
27
|
+
async function loadFile(file, offline = false) {
|
|
28
|
+
return offline ? loadLocalFile(file) : loadWebFile(file);
|
|
29
|
+
}
|
|
21
30
|
exports.default = loadFile;
|
|
@@ -5,10 +5,15 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const fs_1 = __importDefault(require("fs"));
|
|
7
7
|
const path_1 = __importDefault(require("path"));
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
let allFiles;
|
|
9
|
+
let allFolders;
|
|
10
10
|
/** Generate list of files in a directory. */
|
|
11
|
-
function walk(root, folders, gitignores, regexIgnores) {
|
|
11
|
+
function walk(init, root, folders, gitignores, regexIgnores) {
|
|
12
|
+
// Initialise files and folders lists
|
|
13
|
+
if (init) {
|
|
14
|
+
allFiles = new Set();
|
|
15
|
+
allFolders = new Set();
|
|
16
|
+
}
|
|
12
17
|
// Walk tree of a folder
|
|
13
18
|
if (folders.length === 1) {
|
|
14
19
|
const folder = folders[0];
|
|
@@ -36,7 +41,7 @@ function walk(root, folders, gitignores, regexIgnores) {
|
|
|
36
41
|
if (file.endsWith('/')) {
|
|
37
42
|
// Recurse into subfolders
|
|
38
43
|
allFolders.add(path);
|
|
39
|
-
walk(root, [path], gitignores, regexIgnores);
|
|
44
|
+
walk(false, root, [path], gitignores, regexIgnores);
|
|
40
45
|
}
|
|
41
46
|
else {
|
|
42
47
|
// Add relative file path to list
|
|
@@ -47,7 +52,7 @@ function walk(root, folders, gitignores, regexIgnores) {
|
|
|
47
52
|
// Recurse into all folders
|
|
48
53
|
else {
|
|
49
54
|
for (const path of folders) {
|
|
50
|
-
walk(root, [path], gitignores, regexIgnores);
|
|
55
|
+
walk(false, root, [path], gitignores, regexIgnores);
|
|
51
56
|
}
|
|
52
57
|
}
|
|
53
58
|
// Return absolute files and folders lists
|
package/dist/index.js
CHANGED
|
@@ -14,17 +14,17 @@ const load_data_1 = __importDefault(require("./helpers/load-data"));
|
|
|
14
14
|
const read_file_1 = __importDefault(require("./helpers/read-file"));
|
|
15
15
|
const convert_pcre_1 = __importDefault(require("./helpers/convert-pcre"));
|
|
16
16
|
async function analyse(input, opts = {}) {
|
|
17
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s;
|
|
18
|
-
var
|
|
17
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t;
|
|
18
|
+
var _u, _v, _w;
|
|
19
19
|
const useRawContent = opts.fileContent !== undefined;
|
|
20
20
|
input = [input !== null && input !== void 0 ? input : []].flat();
|
|
21
21
|
opts.fileContent = [(_a = opts.fileContent) !== null && _a !== void 0 ? _a : []].flat();
|
|
22
22
|
// Load data from github-linguist web repo
|
|
23
|
-
const langData = await (0, load_data_1.default)('languages.yml').then(js_yaml_1.default.load);
|
|
24
|
-
const vendorData = await (0, load_data_1.default)('vendor.yml').then(js_yaml_1.default.load);
|
|
25
|
-
const docData = await (0, load_data_1.default)('documentation.yml').then(js_yaml_1.default.load);
|
|
26
|
-
const heuristicsData = await (0, load_data_1.default)('heuristics.yml').then(js_yaml_1.default.load);
|
|
27
|
-
const generatedData = await (0, load_data_1.default)('generated.rb').then(text => { var _a; return (_a = text.match(/(?<=name\.match\(\/).+?(?=(?<!\\)\/)/gm)) !== null && _a !== void 0 ? _a : []; });
|
|
23
|
+
const langData = await (0, load_data_1.default)('languages.yml', opts.offline).then(js_yaml_1.default.load);
|
|
24
|
+
const vendorData = await (0, load_data_1.default)('vendor.yml', opts.offline).then(js_yaml_1.default.load);
|
|
25
|
+
const docData = await (0, load_data_1.default)('documentation.yml', opts.offline).then(js_yaml_1.default.load);
|
|
26
|
+
const heuristicsData = await (0, load_data_1.default)('heuristics.yml', opts.offline).then(js_yaml_1.default.load);
|
|
27
|
+
const generatedData = await (0, load_data_1.default)('generated.rb', opts.offline).then(text => { var _a; return (_a = text.match(/(?<=name\.match\(\/).+?(?=(?<!\\)\/)/gm)) !== null && _a !== void 0 ? _a : []; });
|
|
28
28
|
const vendorPaths = [...vendorData, ...docData, ...generatedData];
|
|
29
29
|
// Setup main variables
|
|
30
30
|
const fileAssociations = {};
|
|
@@ -58,7 +58,7 @@ async function analyse(input, opts = {}) {
|
|
|
58
58
|
}
|
|
59
59
|
else {
|
|
60
60
|
// Uses directory on disc
|
|
61
|
-
const data = (0, walk_tree_1.default)(commonRoot, input, gitignores, regexIgnores);
|
|
61
|
+
const data = (0, walk_tree_1.default)(true, commonRoot, input, gitignores, regexIgnores);
|
|
62
62
|
files = data.files;
|
|
63
63
|
folders = data.folders;
|
|
64
64
|
}
|
|
@@ -86,8 +86,9 @@ async function analyse(input, opts = {}) {
|
|
|
86
86
|
if (!useRawContent && opts.checkAttributes) {
|
|
87
87
|
for (const folder of folders) {
|
|
88
88
|
// Skip if folder is marked in gitattributes
|
|
89
|
-
if (relPath(folder) && gitignores.
|
|
89
|
+
if (relPath(folder) && gitignores.ignores(relPath(folder))) {
|
|
90
90
|
continue;
|
|
91
|
+
}
|
|
91
92
|
// Parse gitignores
|
|
92
93
|
const ignoresFile = path_1.default.join(folder, '.gitignore');
|
|
93
94
|
if (opts.checkIgnored && fs_1.default.existsSync(ignoresFile)) {
|
|
@@ -101,10 +102,12 @@ async function analyse(input, opts = {}) {
|
|
|
101
102
|
// Explicit text/binary associations
|
|
102
103
|
const contentTypeMatches = attributesData.matchAll(/^(\S+).*?(-?binary|-?text)(?!=auto)/gm);
|
|
103
104
|
for (const [_line, path, type] of contentTypeMatches) {
|
|
104
|
-
if (['text', '-binary'].includes(type))
|
|
105
|
+
if (['text', '-binary'].includes(type)) {
|
|
105
106
|
customText.add(path);
|
|
106
|
-
|
|
107
|
+
}
|
|
108
|
+
if (['-text', 'binary'].includes(type)) {
|
|
107
109
|
customBinary.add(path);
|
|
110
|
+
}
|
|
108
111
|
}
|
|
109
112
|
// Custom vendor options
|
|
110
113
|
const vendorMatches = attributesData.matchAll(/^(\S+).*[^-]linguist-(vendored|generated|documentation)(?!=false)/gm);
|
|
@@ -117,8 +120,9 @@ async function analyse(input, opts = {}) {
|
|
|
117
120
|
// If specified language is an alias, associate it with its full name
|
|
118
121
|
if (!langData[forcedLang]) {
|
|
119
122
|
const overrideLang = Object.entries(langData).find(entry => { var _a; return (_a = entry[1].aliases) === null || _a === void 0 ? void 0 : _a.includes(forcedLang.toLowerCase()); });
|
|
120
|
-
if (overrideLang)
|
|
123
|
+
if (overrideLang) {
|
|
121
124
|
forcedLang = overrideLang[0];
|
|
125
|
+
}
|
|
122
126
|
}
|
|
123
127
|
const fullPath = relPath(folder) + '/' + path;
|
|
124
128
|
overrides[fullPath] = forcedLang;
|
|
@@ -154,11 +158,11 @@ async function analyse(input, opts = {}) {
|
|
|
154
158
|
if (useRawContent) {
|
|
155
159
|
firstLine = (_e = (_d = (_c = opts.fileContent) === null || _c === void 0 ? void 0 : _c[files.indexOf(file)]) === null || _d === void 0 ? void 0 : _d.split('\n')[0]) !== null && _e !== void 0 ? _e : null;
|
|
156
160
|
}
|
|
157
|
-
else {
|
|
158
|
-
if (!fs_1.default.existsSync(file) || fs_1.default.lstatSync(file).isDirectory())
|
|
159
|
-
continue;
|
|
161
|
+
else if (fs_1.default.existsSync(file) && !fs_1.default.lstatSync(file).isDirectory()) {
|
|
160
162
|
firstLine = await (0, read_file_1.default)(file, true).catch(() => null);
|
|
161
163
|
}
|
|
164
|
+
else
|
|
165
|
+
continue;
|
|
162
166
|
// Skip if file is unreadable
|
|
163
167
|
if (firstLine === null)
|
|
164
168
|
continue;
|
|
@@ -175,10 +179,11 @@ async function analyse(input, opts = {}) {
|
|
|
175
179
|
const matchesLang = firstLine.toLowerCase().match(langMatcher(lang));
|
|
176
180
|
const matchesAlias = (_g = data.aliases) === null || _g === void 0 ? void 0 : _g.some(lang => firstLine.toLowerCase().match(langMatcher(lang)));
|
|
177
181
|
// Add language
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
if (
|
|
182
|
+
const interpretorCheck = opts.checkShebang && matchesInterpretor;
|
|
183
|
+
const modelineCheck = opts.checkModeline && (matchesLang || matchesAlias);
|
|
184
|
+
if (interpretorCheck || modelineCheck) {
|
|
181
185
|
matches.push(lang);
|
|
186
|
+
}
|
|
182
187
|
}
|
|
183
188
|
if (matches.length) {
|
|
184
189
|
// Add explicitly-identified language
|
|
@@ -190,7 +195,7 @@ async function analyse(input, opts = {}) {
|
|
|
190
195
|
}
|
|
191
196
|
// Check override for manual language classification
|
|
192
197
|
if (!useRawContent && !opts.quick && opts.checkAttributes) {
|
|
193
|
-
const isOverridden = (path) => (0, ignore_1.default)().add(path).
|
|
198
|
+
const isOverridden = (path) => (0, ignore_1.default)().add(path).ignores(relPath(file));
|
|
194
199
|
const match = overridesArray.find(item => isOverridden(item[0]));
|
|
195
200
|
if (match) {
|
|
196
201
|
const forcedLang = match[1];
|
|
@@ -213,12 +218,14 @@ async function analyse(input, opts = {}) {
|
|
|
213
218
|
for (const lang in langData) {
|
|
214
219
|
// Check if extension is a match
|
|
215
220
|
const matchesExt = (_j = langData[lang].extensions) === null || _j === void 0 ? void 0 : _j.some(ext => file.toLowerCase().endsWith(ext.toLowerCase()));
|
|
216
|
-
if (matchesExt)
|
|
221
|
+
if (matchesExt) {
|
|
217
222
|
addResult(file, lang);
|
|
223
|
+
}
|
|
218
224
|
}
|
|
219
225
|
// Fallback to null if no language matches
|
|
220
|
-
if (!fileAssociations[file])
|
|
226
|
+
if (!fileAssociations[file]) {
|
|
221
227
|
addResult(file, null);
|
|
228
|
+
}
|
|
222
229
|
}
|
|
223
230
|
// Narrow down file associations to the best fit
|
|
224
231
|
for (const file in fileAssociations) {
|
|
@@ -229,8 +236,8 @@ async function analyse(input, opts = {}) {
|
|
|
229
236
|
}
|
|
230
237
|
// Skip binary files
|
|
231
238
|
if (!useRawContent && !opts.keepBinary) {
|
|
232
|
-
const isCustomText = customText.
|
|
233
|
-
const isCustomBinary = customBinary.
|
|
239
|
+
const isCustomText = customText.ignores(relPath(file));
|
|
240
|
+
const isCustomBinary = customBinary.ignores(relPath(file));
|
|
234
241
|
const isBinaryExt = binary_extensions_1.default.some(ext => file.endsWith('.' + ext));
|
|
235
242
|
if (!isCustomText && (isCustomBinary || isBinaryExt || await (0, isbinaryfile_1.isBinaryFile)(file))) {
|
|
236
243
|
continue;
|
|
@@ -262,8 +269,16 @@ async function analyse(input, opts = {}) {
|
|
|
262
269
|
normalise(heuristic.pattern);
|
|
263
270
|
if (heuristic.named_pattern)
|
|
264
271
|
normalise(heuristicsData.named_patterns[heuristic.named_pattern]);
|
|
272
|
+
if (heuristic.and) {
|
|
273
|
+
for (const data of heuristic.and) {
|
|
274
|
+
if (data.pattern)
|
|
275
|
+
normalise(data.pattern);
|
|
276
|
+
if (data.named_pattern)
|
|
277
|
+
normalise(heuristicsData.named_patterns[data.named_pattern]);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
265
280
|
// Check file contents and apply heuristic patterns
|
|
266
|
-
const fileContent = await (0, read_file_1.default)(file).catch(() => null);
|
|
281
|
+
const fileContent = ((_l = opts.fileContent) === null || _l === void 0 ? void 0 : _l.length) ? opts.fileContent[files.indexOf(file)] : await (0, read_file_1.default)(file).catch(() => null);
|
|
267
282
|
if (fileContent === null)
|
|
268
283
|
continue;
|
|
269
284
|
if (!patterns.length || patterns.some(pattern => (0, convert_pcre_1.default)(pattern).test(fileContent))) {
|
|
@@ -273,23 +288,26 @@ async function analyse(input, opts = {}) {
|
|
|
273
288
|
}
|
|
274
289
|
}
|
|
275
290
|
// If no heuristics, assign a language
|
|
276
|
-
(
|
|
291
|
+
(_m = (_u = results.files.results)[file]) !== null && _m !== void 0 ? _m : (_u[file] = fileAssociations[file][0]);
|
|
277
292
|
}
|
|
278
293
|
// Skip specified categories
|
|
279
|
-
if ((
|
|
294
|
+
if ((_o = opts.categories) === null || _o === void 0 ? void 0 : _o.length) {
|
|
280
295
|
const categories = ['data', 'markup', 'programming', 'prose'];
|
|
281
296
|
const hiddenCategories = categories.filter(cat => !opts.categories.includes(cat));
|
|
282
297
|
for (const [file, lang] of Object.entries(results.files.results)) {
|
|
283
|
-
if (!hiddenCategories.some(cat => { var _a; return lang && ((_a = langData[lang]) === null || _a === void 0 ? void 0 : _a.type) === cat; }))
|
|
298
|
+
if (!hiddenCategories.some(cat => { var _a; return lang && ((_a = langData[lang]) === null || _a === void 0 ? void 0 : _a.type) === cat; })) {
|
|
284
299
|
continue;
|
|
300
|
+
}
|
|
285
301
|
delete results.files.results[file];
|
|
286
|
-
if (lang)
|
|
302
|
+
if (lang) {
|
|
287
303
|
delete results.languages.results[lang];
|
|
304
|
+
}
|
|
288
305
|
}
|
|
289
306
|
for (const category of hiddenCategories) {
|
|
290
307
|
for (const [lang, { type }] of Object.entries(results.languages.results)) {
|
|
291
|
-
if (type === category)
|
|
308
|
+
if (type === category) {
|
|
292
309
|
delete results.languages.results[lang];
|
|
310
|
+
}
|
|
293
311
|
}
|
|
294
312
|
}
|
|
295
313
|
}
|
|
@@ -298,8 +316,9 @@ async function analyse(input, opts = {}) {
|
|
|
298
316
|
const newMap = {};
|
|
299
317
|
for (const [file, lang] of Object.entries(results.files.results)) {
|
|
300
318
|
let relPath = path_1.default.relative(process.cwd(), file).replace(/\\/g, '/');
|
|
301
|
-
if (!relPath.startsWith('../'))
|
|
319
|
+
if (!relPath.startsWith('../')) {
|
|
302
320
|
relPath = './' + relPath;
|
|
321
|
+
}
|
|
303
322
|
newMap[relPath] = lang;
|
|
304
323
|
}
|
|
305
324
|
results.files.results = newMap;
|
|
@@ -308,23 +327,24 @@ async function analyse(input, opts = {}) {
|
|
|
308
327
|
for (const [file, lang] of Object.entries(results.files.results)) {
|
|
309
328
|
if (lang && !langData[lang])
|
|
310
329
|
continue;
|
|
311
|
-
const fileSize = (
|
|
330
|
+
const fileSize = (_r = (_q = (_p = opts.fileContent) === null || _p === void 0 ? void 0 : _p[files.indexOf(file)]) === null || _q === void 0 ? void 0 : _q.length) !== null && _r !== void 0 ? _r : fs_1.default.statSync(file).size;
|
|
312
331
|
results.files.bytes += fileSize;
|
|
313
332
|
// If no language found, add extension in other section
|
|
314
333
|
if (!lang) {
|
|
315
334
|
const ext = path_1.default.extname(file);
|
|
316
335
|
const unknownType = ext === '' ? 'filenames' : 'extensions';
|
|
317
336
|
const name = ext === '' ? path_1.default.basename(file) : ext;
|
|
318
|
-
(
|
|
337
|
+
(_s = (_v = results.unknown[unknownType])[name]) !== null && _s !== void 0 ? _s : (_v[name] = 0);
|
|
319
338
|
results.unknown[unknownType][name] += fileSize;
|
|
320
339
|
results.unknown.bytes += fileSize;
|
|
321
340
|
continue;
|
|
322
341
|
}
|
|
323
342
|
// Add language and bytes data to corresponding section
|
|
324
343
|
const { type } = langData[lang];
|
|
325
|
-
(
|
|
326
|
-
if (opts.childLanguages)
|
|
344
|
+
(_t = (_w = results.languages.results)[lang]) !== null && _t !== void 0 ? _t : (_w[lang] = { type, bytes: 0, color: langData[lang].color });
|
|
345
|
+
if (opts.childLanguages) {
|
|
327
346
|
results.languages.results[lang].parent = langData[lang].group;
|
|
347
|
+
}
|
|
328
348
|
results.languages.results[lang].bytes += fileSize;
|
|
329
349
|
results.languages.bytes += fileSize;
|
|
330
350
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
- ^[Dd]ocs?/
|
|
2
|
+
- (^|/)[Dd]ocumentation/
|
|
3
|
+
- (^|/)[Gg]roovydoc/
|
|
4
|
+
- (^|/)[Jj]avadoc/
|
|
5
|
+
- ^[Mm]an/
|
|
6
|
+
- ^[Ee]xamples/
|
|
7
|
+
- ^[Dd]emos?/
|
|
8
|
+
- (^|/)inst/doc/- (^|/)CITATION(\.cff|(S)?(\.(bib|md))?)$
|
|
9
|
+
- (^|/)CHANGE(S|LOG)?(\.|$)
|
|
10
|
+
- (^|/)CONTRIBUTING(\.|$)
|
|
11
|
+
- (^|/)COPYING(\.|$)
|
|
12
|
+
- (^|/)INSTALL(\.|$)
|
|
13
|
+
- (^|/)LICEN[CS]E(\.|$)
|
|
14
|
+
- (^|/)[Ll]icen[cs]e(\.|$)
|
|
15
|
+
- (^|/)README(\.|$)
|
|
16
|
+
- (^|/)[Rr]eadme(\.|$)
|
|
17
|
+
- ^[Ss]amples?/
|