linguist-js 2.3.1 → 2.3.2
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 -4
- package/dist/helpers/walk-tree.js +37 -15
- package/dist/index.js +41 -26
- package/package.json +2 -1
package/dist/cli.js
CHANGED
|
@@ -6,7 +6,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
const VERSION = require('../package.json').version;
|
|
7
7
|
const commander_1 = require("commander");
|
|
8
8
|
const index_1 = __importDefault(require("./index"));
|
|
9
|
-
const walk_tree_1 = __importDefault(require("./helpers/walk-tree"));
|
|
10
9
|
const colouredMsg = ([r, g, b], msg) => `\u001B[${38};2;${r};${g};${b}m${msg}${'\u001b[0m'}`;
|
|
11
10
|
const hexToRgb = (hex) => [parseInt(hex.slice(1, 3), 16), parseInt(hex.slice(3, 5), 16), parseInt(hex.slice(5, 7), 16)];
|
|
12
11
|
commander_1.program
|
|
@@ -53,13 +52,11 @@ if (args.analyze)
|
|
|
53
52
|
const root = args.analyze === true ? '.' : args.analyze;
|
|
54
53
|
const data = await (0, index_1.default)(root, args);
|
|
55
54
|
const { files, languages, unknown } = data;
|
|
56
|
-
// Get file count
|
|
57
|
-
const totalFiles = (0, walk_tree_1.default)(root).files.length;
|
|
58
55
|
// Print output
|
|
59
56
|
if (!args.json) {
|
|
60
57
|
const sortedEntries = Object.entries(languages.results).sort((a, b) => a[1].bytes < b[1].bytes ? +1 : -1);
|
|
61
58
|
const totalBytes = languages.bytes;
|
|
62
|
-
console.log(`\n Analysed ${files.bytes.toLocaleString()} B from ${
|
|
59
|
+
console.log(`\n Analysed ${files.bytes.toLocaleString()} B from ${files.count} files with linguist-js`);
|
|
63
60
|
console.log(`\n Language analysis results:`);
|
|
64
61
|
let count = 0;
|
|
65
62
|
if (sortedEntries.length === 0)
|
|
@@ -8,28 +8,50 @@ const path_1 = __importDefault(require("path"));
|
|
|
8
8
|
const allFiles = new Set();
|
|
9
9
|
const allFolders = new Set();
|
|
10
10
|
/** Generate list of files in a directory. */
|
|
11
|
-
function walk(
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
11
|
+
function walk(root, folders, ignored = []) {
|
|
12
|
+
// Switch out paths that expect being in root
|
|
13
|
+
ignored = ignored.map(match => match.replace(/^\^/, '^\\./'));
|
|
14
|
+
// Walk tree of a folder
|
|
15
|
+
if (folders.length === 1) {
|
|
16
|
+
const folder = folders[0];
|
|
17
|
+
// Get list of files and folders inside this folder
|
|
18
|
+
const files = fs_1.default.readdirSync(folder).map(file => {
|
|
19
|
+
// Create path relative to root
|
|
20
|
+
const base = path_1.default.resolve(folder, file).replace(/\\/g, '/').replace(root, '.');
|
|
21
|
+
// Add trailing slash to mark directories
|
|
22
|
+
const isDir = fs_1.default.lstatSync(path_1.default.resolve(root, base)).isDirectory();
|
|
23
|
+
return isDir ? `${base}/` : base;
|
|
24
|
+
});
|
|
25
|
+
// Loop through files and folders
|
|
18
26
|
for (const file of files) {
|
|
19
|
-
|
|
20
|
-
|
|
27
|
+
// Create absolute path for disc operations
|
|
28
|
+
const path = path_1.default.resolve(root, file).replace(/\\/g, '/');
|
|
29
|
+
// Skip if nonexistant or ignored
|
|
30
|
+
if (!fs_1.default.existsSync(path) || ignored.some(pattern => RegExp(pattern).test(file)))
|
|
21
31
|
continue;
|
|
22
|
-
|
|
23
|
-
|
|
32
|
+
// Add absolute folder path to list
|
|
33
|
+
allFolders.add(path_1.default.resolve(folder).replace(/\\/g, '/'));
|
|
34
|
+
// Check if this is a folder or file
|
|
35
|
+
if (file.endsWith('/')) {
|
|
36
|
+
// Recurse into subfolders
|
|
24
37
|
allFolders.add(path);
|
|
25
|
-
walk(path, ignored);
|
|
26
|
-
|
|
38
|
+
walk(root, [path], ignored);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
// Add relative file path to list
|
|
42
|
+
allFiles.add(path);
|
|
27
43
|
}
|
|
28
|
-
allFiles.add(path);
|
|
29
44
|
}
|
|
30
45
|
}
|
|
46
|
+
// Recurse into all folders
|
|
47
|
+
else {
|
|
48
|
+
for (const path of folders) {
|
|
49
|
+
walk(root, [path], ignored);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// Return absolute files and folders lists
|
|
31
53
|
return {
|
|
32
|
-
files: [...allFiles],
|
|
54
|
+
files: [...allFiles].map(file => file.replace(/^\./, root)),
|
|
33
55
|
folders: [...allFolders],
|
|
34
56
|
};
|
|
35
57
|
}
|
package/dist/index.js
CHANGED
|
@@ -6,6 +6,7 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
6
6
|
const path_1 = __importDefault(require("path"));
|
|
7
7
|
const js_yaml_1 = __importDefault(require("js-yaml"));
|
|
8
8
|
const glob_to_regexp_1 = __importDefault(require("glob-to-regexp"));
|
|
9
|
+
const common_path_prefix_1 = __importDefault(require("common-path-prefix"));
|
|
9
10
|
const binary_extensions_1 = __importDefault(require("binary-extensions"));
|
|
10
11
|
const isbinaryfile_1 = require("isbinaryfile");
|
|
11
12
|
const walk_tree_1 = __importDefault(require("./helpers/walk-tree"));
|
|
@@ -14,14 +15,19 @@ const read_file_1 = __importDefault(require("./helpers/read-file"));
|
|
|
14
15
|
const convert_pcre_1 = __importDefault(require("./helpers/convert-pcre"));
|
|
15
16
|
const convert_glob_1 = __importDefault(require("./helpers/convert-glob"));
|
|
16
17
|
async function analyse(input, opts = {}) {
|
|
17
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q
|
|
18
|
-
var _s, _t
|
|
18
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
|
|
19
|
+
var _r, _s, _t;
|
|
19
20
|
const useRawContent = opts.fileContent !== undefined;
|
|
21
|
+
input = [input !== null && input !== void 0 ? input : []].flat();
|
|
22
|
+
opts.fileContent = [(_a = opts.fileContent) !== null && _a !== void 0 ? _a : []].flat();
|
|
23
|
+
// Load data from github-linguist web repo
|
|
20
24
|
const langData = await (0, load_data_1.default)('languages.yml').then(js_yaml_1.default.load);
|
|
21
25
|
const vendorData = await (0, load_data_1.default)('vendor.yml').then(js_yaml_1.default.load);
|
|
26
|
+
const docData = await (0, load_data_1.default)('documentation.yml').then(js_yaml_1.default.load);
|
|
22
27
|
const heuristicsData = await (0, load_data_1.default)('heuristics.yml').then(js_yaml_1.default.load);
|
|
23
28
|
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 : []; });
|
|
24
|
-
vendorData
|
|
29
|
+
const vendorPaths = [...vendorData, ...docData, ...generatedData];
|
|
30
|
+
// Setup main variables
|
|
25
31
|
const fileAssociations = {};
|
|
26
32
|
const extensions = {};
|
|
27
33
|
const overrides = {};
|
|
@@ -30,25 +36,34 @@ async function analyse(input, opts = {}) {
|
|
|
30
36
|
languages: { count: 0, bytes: 0, results: {} },
|
|
31
37
|
unknown: { count: 0, bytes: 0, extensions: {}, filenames: {} },
|
|
32
38
|
};
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
39
|
+
// Prepare list of ignored files
|
|
40
|
+
const ignoredFiles = ['(^|\\/)\\.git\\/'];
|
|
41
|
+
if (!opts.keepVendored) {
|
|
42
|
+
ignoredFiles.push(...vendorPaths);
|
|
43
|
+
}
|
|
44
|
+
if (opts.ignoredFiles) {
|
|
45
|
+
ignoredFiles.push(...opts.ignoredFiles.map(path => (0, glob_to_regexp_1.default)('*' + path + '*', { extended: true }).source));
|
|
46
|
+
}
|
|
47
|
+
// Load file paths and folders
|
|
38
48
|
let files, folders;
|
|
39
|
-
if (
|
|
40
|
-
|
|
41
|
-
files =
|
|
49
|
+
if (useRawContent) {
|
|
50
|
+
// Uses raw file content
|
|
51
|
+
files = input;
|
|
42
52
|
folders = [''];
|
|
43
53
|
}
|
|
44
54
|
else {
|
|
45
|
-
|
|
46
|
-
|
|
55
|
+
// Uses directory on disc
|
|
56
|
+
// Set a common root path so that vendor paths do not incorrectly match parent folders
|
|
57
|
+
const resolvedInput = input.map(path => path_1.default.resolve(path).replace(/\\/g, '/'));
|
|
58
|
+
const commonRoot = (input.length > 1 ? (0, common_path_prefix_1.default)(resolvedInput) : resolvedInput[0]).replace(/\/?$/, '');
|
|
59
|
+
const data = (0, walk_tree_1.default)(commonRoot, input, ignoredFiles);
|
|
60
|
+
files = data.files;
|
|
61
|
+
folders = data.folders;
|
|
47
62
|
}
|
|
48
63
|
// Apply aliases
|
|
49
64
|
opts = { checkIgnored: !opts.quick, checkAttributes: !opts.quick, checkHeuristics: !opts.quick, checkShebang: !opts.quick, ...opts };
|
|
50
65
|
// Ignore specific languages
|
|
51
|
-
for (const lang of (
|
|
66
|
+
for (const lang of (_b = opts.ignoredLanguages) !== null && _b !== void 0 ? _b : []) {
|
|
52
67
|
for (const key in langData) {
|
|
53
68
|
if (lang.toLowerCase() === key.toLowerCase()) {
|
|
54
69
|
delete langData[key];
|
|
@@ -60,7 +75,7 @@ async function analyse(input, opts = {}) {
|
|
|
60
75
|
const customIgnored = [];
|
|
61
76
|
const customBinary = [];
|
|
62
77
|
const customText = [];
|
|
63
|
-
if (!useRawContent &&
|
|
78
|
+
if (!useRawContent && opts.checkAttributes) {
|
|
64
79
|
for (const folder of folders) {
|
|
65
80
|
// Skip if folder is marked in gitattributes
|
|
66
81
|
if (customIgnored.some(path => (0, convert_glob_1.default)(path).test(folder)))
|
|
@@ -126,8 +141,8 @@ async function analyse(input, opts = {}) {
|
|
|
126
141
|
// List all languages that could be associated with a given file
|
|
127
142
|
for (const file of files) {
|
|
128
143
|
let firstLine;
|
|
129
|
-
if (
|
|
130
|
-
firstLine = (
|
|
144
|
+
if (useRawContent) {
|
|
145
|
+
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;
|
|
131
146
|
}
|
|
132
147
|
else {
|
|
133
148
|
if (!fs_1.default.existsSync(file) || fs_1.default.lstatSync(file).isDirectory())
|
|
@@ -160,7 +175,7 @@ async function analyse(input, opts = {}) {
|
|
|
160
175
|
let skipExts = false;
|
|
161
176
|
for (const lang in langData) {
|
|
162
177
|
// Check if filename is a match
|
|
163
|
-
const matchesName = (
|
|
178
|
+
const matchesName = (_f = langData[lang].filenames) === null || _f === void 0 ? void 0 : _f.some(name => path_1.default.basename(file.toLowerCase()) === name.toLowerCase());
|
|
164
179
|
if (matchesName) {
|
|
165
180
|
addResult(file, lang);
|
|
166
181
|
skipExts = true;
|
|
@@ -169,7 +184,7 @@ async function analyse(input, opts = {}) {
|
|
|
169
184
|
if (!skipExts)
|
|
170
185
|
for (const lang in langData) {
|
|
171
186
|
// Check if extension is a match
|
|
172
|
-
const matchesExt = (
|
|
187
|
+
const matchesExt = (_g = langData[lang].extensions) === null || _g === void 0 ? void 0 : _g.some(ext => file.toLowerCase().endsWith(ext.toLowerCase()));
|
|
173
188
|
if (matchesExt)
|
|
174
189
|
addResult(file, lang);
|
|
175
190
|
}
|
|
@@ -202,14 +217,14 @@ async function analyse(input, opts = {}) {
|
|
|
202
217
|
heuristic.language = heuristic.language[0];
|
|
203
218
|
}
|
|
204
219
|
// Make sure the results includes this language
|
|
205
|
-
const languageGroup = (
|
|
220
|
+
const languageGroup = (_h = langData[heuristic.language]) === null || _h === void 0 ? void 0 : _h.group;
|
|
206
221
|
const matchesLang = fileAssociations[file].includes(heuristic.language);
|
|
207
222
|
const matchesParent = languageGroup && fileAssociations[file].includes(languageGroup);
|
|
208
223
|
if (!matchesLang && !matchesParent)
|
|
209
224
|
continue;
|
|
210
225
|
// Normalise heuristic data
|
|
211
226
|
const patterns = [];
|
|
212
|
-
const normalise = (contents) => patterns.push(...
|
|
227
|
+
const normalise = (contents) => patterns.push(...[contents].flat());
|
|
213
228
|
if (heuristic.pattern)
|
|
214
229
|
normalise(heuristic.pattern);
|
|
215
230
|
if (heuristic.named_pattern)
|
|
@@ -225,10 +240,10 @@ async function analyse(input, opts = {}) {
|
|
|
225
240
|
}
|
|
226
241
|
}
|
|
227
242
|
// If no heuristics, assign a language
|
|
228
|
-
(
|
|
243
|
+
(_j = (_r = results.files.results)[file]) !== null && _j !== void 0 ? _j : (_r[file] = fileAssociations[file][0]);
|
|
229
244
|
}
|
|
230
245
|
// Skip specified categories
|
|
231
|
-
if ((
|
|
246
|
+
if ((_k = opts.categories) === null || _k === void 0 ? void 0 : _k.length) {
|
|
232
247
|
const categories = ['data', 'markup', 'programming', 'prose'];
|
|
233
248
|
const hiddenCategories = categories.filter(cat => !opts.categories.includes(cat));
|
|
234
249
|
for (const [file, lang] of Object.entries(results.files.results)) {
|
|
@@ -260,21 +275,21 @@ async function analyse(input, opts = {}) {
|
|
|
260
275
|
for (const [file, lang] of Object.entries(results.files.results)) {
|
|
261
276
|
if (lang && !langData[lang])
|
|
262
277
|
continue;
|
|
263
|
-
const fileSize = (
|
|
278
|
+
const fileSize = (_o = (_m = (_l = opts.fileContent) === null || _l === void 0 ? void 0 : _l[files.indexOf(file)]) === null || _m === void 0 ? void 0 : _m.length) !== null && _o !== void 0 ? _o : fs_1.default.statSync(file).size;
|
|
264
279
|
results.files.bytes += fileSize;
|
|
265
280
|
// If no language found, add extension in other section
|
|
266
281
|
if (!lang) {
|
|
267
282
|
const ext = path_1.default.extname(file);
|
|
268
283
|
const unknownType = ext === '' ? 'filenames' : 'extensions';
|
|
269
284
|
const name = ext === '' ? path_1.default.basename(file) : ext;
|
|
270
|
-
(
|
|
285
|
+
(_p = (_s = results.unknown[unknownType])[name]) !== null && _p !== void 0 ? _p : (_s[name] = 0);
|
|
271
286
|
results.unknown[unknownType][name] += fileSize;
|
|
272
287
|
results.unknown.bytes += fileSize;
|
|
273
288
|
continue;
|
|
274
289
|
}
|
|
275
290
|
// Add language and bytes data to corresponding section
|
|
276
291
|
const { type } = langData[lang];
|
|
277
|
-
(
|
|
292
|
+
(_q = (_t = results.languages.results)[lang]) !== null && _q !== void 0 ? _q : (_t[lang] = { type, bytes: 0, color: langData[lang].color });
|
|
278
293
|
if (opts.childLanguages)
|
|
279
294
|
results.languages.results[lang].parent = langData[lang].group;
|
|
280
295
|
results.languages.results[lang].bytes += fileSize;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "linguist-js",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.2",
|
|
4
4
|
"description": "Analyse languages used in a folder. Powered by GitHub Linguist, although it doesn't need to be installed.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"binary-extensions": "^2.2.0",
|
|
40
40
|
"commander": "^9.0.0",
|
|
41
|
+
"common-path-prefix": "^3.0.0",
|
|
41
42
|
"cross-fetch": "^3.1.5",
|
|
42
43
|
"glob-to-regexp": "~0.4.1",
|
|
43
44
|
"isbinaryfile": "^4.0.8",
|