linguist-js 2.1.1 → 2.2.0
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 +24 -20
- package/dist/helpers/convert-glob.js +11 -0
- package/dist/helpers/convert-pcre.js +1 -1
- package/dist/index.js +37 -10
- package/dist/types.d.ts +1 -0
- package/package.json +4 -5
- package/readme.md +21 -16
package/dist/cli.js
CHANGED
|
@@ -6,6 +6,7 @@ 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"));
|
|
9
10
|
const colouredMsg = ([r, g, b], msg) => `\u001B[${38};2;${r};${g};${b}m${msg}${'\u001b[0m'}`;
|
|
10
11
|
const hexToRgb = (hex) => [parseInt(hex.slice(1, 3), 16), parseInt(hex.slice(3, 5), 16), parseInt(hex.slice(5, 7), 16)];
|
|
11
12
|
commander_1.program
|
|
@@ -21,6 +22,7 @@ commander_1.program
|
|
|
21
22
|
.option('-q|--quick [bool]', 'Skip complex language analysis (alias for -{A|I|H|S}=false)', 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)
|
|
25
|
+
.option('-r|--relativePaths [bool]', 'Convert absolute file paths to relative', false)
|
|
24
26
|
.option('-A|--checkAttributes [bool]', 'Force the checking of gitattributes files', true)
|
|
25
27
|
.option('-I|--checkIgnored [bool]', 'Force the checking of gitignore files', true)
|
|
26
28
|
.option('-H|--checkHeuristics [bool]', 'Apply heuristics to ambiguous languages', true)
|
|
@@ -49,21 +51,26 @@ if (args.analyze)
|
|
|
49
51
|
(async () => {
|
|
50
52
|
// Fetch language data
|
|
51
53
|
const root = args.analyze === true ? '.' : args.analyze;
|
|
52
|
-
const
|
|
54
|
+
const data = await (0, index_1.default)(root, args);
|
|
55
|
+
const { files, languages, unknown } = data;
|
|
56
|
+
// Get file count
|
|
57
|
+
let totalFiles = (0, walk_tree_1.default)(root).files.length;
|
|
53
58
|
// Print output
|
|
54
59
|
if (!args.json) {
|
|
55
60
|
const sortedEntries = Object.entries(languages.results).sort((a, b) => a[1].bytes < b[1].bytes ? +1 : -1);
|
|
56
61
|
const totalBytes = languages.bytes;
|
|
57
|
-
console.log(
|
|
62
|
+
console.log(`\n Analysed ${files.bytes.toLocaleString()} B from ${totalFiles} files (${totalFiles - files.count} ignored) with linguist-js`);
|
|
58
63
|
console.log(`\n Language analysis results:`);
|
|
59
|
-
let
|
|
64
|
+
let count = 0;
|
|
65
|
+
if (sortedEntries.length === 0)
|
|
66
|
+
console.log(` None`);
|
|
60
67
|
for (const [lang, { bytes, color }] of sortedEntries) {
|
|
61
68
|
const fmtd = {
|
|
62
|
-
index: (++
|
|
69
|
+
index: (++count).toString().padStart(2, ' '),
|
|
63
70
|
lang: lang.padEnd(24, ' '),
|
|
64
71
|
percent: (bytes / (totalBytes || 1) * 100).toFixed(2).padStart(5, ' '),
|
|
65
72
|
bytes: bytes.toLocaleString().padStart(10, ' '),
|
|
66
|
-
icon: colouredMsg(hexToRgb(color !== null && color !== void 0 ? color : '#
|
|
73
|
+
icon: colouredMsg(hexToRgb(color !== null && color !== void 0 ? color : '#ededed'), '\u2588'),
|
|
67
74
|
};
|
|
68
75
|
console.log(` ${fmtd.index}. ${fmtd.icon} ${fmtd.lang} ${fmtd.percent}% ${fmtd.bytes} B`);
|
|
69
76
|
}
|
|
@@ -74,26 +81,23 @@ if (args.analyze)
|
|
|
74
81
|
console.log(` '${name}': ${bytes.toLocaleString()} B`);
|
|
75
82
|
}
|
|
76
83
|
for (const [ext, bytes] of Object.entries(unknown.extensions)) {
|
|
77
|
-
console.log(` '
|
|
84
|
+
console.log(` '*${ext}': ${bytes.toLocaleString()} B`);
|
|
78
85
|
}
|
|
79
86
|
console.log(` Total: ${unknown.bytes.toLocaleString()} B`);
|
|
80
87
|
}
|
|
81
88
|
}
|
|
82
|
-
else {
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
throw Error(`TraversalError: Key '${part}' cannot be found on output object.`);
|
|
90
|
-
nestedData = nestedData[part];
|
|
91
|
-
}
|
|
92
|
-
console.log(nestedData);
|
|
93
|
-
}
|
|
94
|
-
else {
|
|
95
|
-
console.log(JSON.stringify(data, null, 2).replace(/{\s+"type".+?}/sg, obj => obj.replace(/\n\s+/g, ' ')));
|
|
89
|
+
else if (args.tree) {
|
|
90
|
+
const treeParts = args.tree.split('.');
|
|
91
|
+
let nestedData = data;
|
|
92
|
+
for (const part of treeParts) {
|
|
93
|
+
if (!nestedData[part])
|
|
94
|
+
throw Error(`TraversalError: Key '${part}' cannot be found on output object.`);
|
|
95
|
+
nestedData = nestedData[part];
|
|
96
96
|
}
|
|
97
|
+
console.log(nestedData);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
console.dir(data, { depth: null });
|
|
97
101
|
}
|
|
98
102
|
})();
|
|
99
103
|
else {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const glob_to_regexp_1 = __importDefault(require("glob-to-regexp"));
|
|
7
|
+
function parseGitGlob(path) {
|
|
8
|
+
const globPath = `**/${path}`.replace(/\\/g, '/').replace(/\[:(space|digit):\]/g, (_, val) => `\\${val[0]}`);
|
|
9
|
+
return (0, glob_to_regexp_1.default)(globPath, { globstar: true, extended: true });
|
|
10
|
+
}
|
|
11
|
+
exports.default = parseGitGlob;
|
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
/** Convert a PCRE regex into JS. */
|
|
4
4
|
function pcre(regex) {
|
|
5
5
|
let finalRegex = regex;
|
|
6
|
-
|
|
6
|
+
const finalFlags = new Set();
|
|
7
7
|
// Convert inline flag declarations
|
|
8
8
|
const inlineMatches = regex.matchAll(/\?([a-z]):/g);
|
|
9
9
|
const startMatches = regex.matchAll(/\(\?([a-z]+)\)/g);
|
package/dist/index.js
CHANGED
|
@@ -12,7 +12,7 @@ const walk_tree_1 = __importDefault(require("./helpers/walk-tree"));
|
|
|
12
12
|
const load_data_1 = __importDefault(require("./helpers/load-data"));
|
|
13
13
|
const read_file_1 = __importDefault(require("./helpers/read-file"));
|
|
14
14
|
const convert_pcre_1 = __importDefault(require("./helpers/convert-pcre"));
|
|
15
|
-
const
|
|
15
|
+
const convert_glob_1 = __importDefault(require("./helpers/convert-glob"));
|
|
16
16
|
async function analyse(input, opts = {}) {
|
|
17
17
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
18
18
|
var _k, _l, _m;
|
|
@@ -31,7 +31,7 @@ async function analyse(input, opts = {}) {
|
|
|
31
31
|
};
|
|
32
32
|
const ignoredFiles = [
|
|
33
33
|
/\/\.git\//,
|
|
34
|
-
opts.keepVendored ? [] : vendorData.map(path => (
|
|
34
|
+
opts.keepVendored ? [] : vendorData.map(path => RegExp(path)),
|
|
35
35
|
(_b = (_a = opts.ignoredFiles) === null || _a === void 0 ? void 0 : _a.map(path => (0, glob_to_regexp_1.default)('*' + path + '*', { extended: true }))) !== null && _b !== void 0 ? _b : [],
|
|
36
36
|
].flat();
|
|
37
37
|
let { files, folders } = (0, walk_tree_1.default)(input !== null && input !== void 0 ? input : '.', ignoredFiles);
|
|
@@ -48,27 +48,38 @@ async function analyse(input, opts = {}) {
|
|
|
48
48
|
}
|
|
49
49
|
// Load gitattributes
|
|
50
50
|
const customIgnored = [];
|
|
51
|
+
const customBinary = [];
|
|
52
|
+
const customText = [];
|
|
51
53
|
if (!opts.quick) {
|
|
52
54
|
for (const folder of folders) {
|
|
53
55
|
// Skip if folder is marked in gitattributes
|
|
54
|
-
if (customIgnored.some(path => (0,
|
|
56
|
+
if (customIgnored.some(path => (0, convert_glob_1.default)(path).test(folder)))
|
|
55
57
|
continue;
|
|
56
58
|
// Parse gitignores
|
|
57
59
|
const ignoresFile = path_1.default.join(folder, '.gitignore');
|
|
58
60
|
if (opts.checkIgnored && fs_1.default.existsSync(ignoresFile)) {
|
|
59
61
|
const ignoresData = await (0, read_file_1.default)(ignoresFile);
|
|
60
62
|
const ignoresList = ignoresData.split(/\r?\n/).filter(line => line.trim() && !line.startsWith('#'));
|
|
61
|
-
const ignoredPaths = ignoresList.map(path =>
|
|
62
|
-
customIgnored.push(...ignoredPaths
|
|
63
|
+
const ignoredPaths = ignoresList.map(path => (0, convert_glob_1.default)(path).source);
|
|
64
|
+
customIgnored.push(...ignoredPaths);
|
|
63
65
|
}
|
|
64
66
|
// Parse gitattributes
|
|
65
67
|
const attributesFile = path_1.default.join(folder, '.gitattributes');
|
|
66
68
|
if (opts.checkAttributes && fs_1.default.existsSync(attributesFile)) {
|
|
67
69
|
const attributesData = await (0, read_file_1.default)(attributesFile);
|
|
70
|
+
const relPathToRegex = (path) => (0, convert_glob_1.default)(path).source.substr(1).replace(folder, '');
|
|
71
|
+
// Explicit text/binary associations
|
|
72
|
+
const contentTypeMatches = attributesData.matchAll(/^(\S+).*?(-?binary|-?text)/gm);
|
|
73
|
+
for (const [_line, path, type] of contentTypeMatches) {
|
|
74
|
+
if (['text', '-binary'].includes(type))
|
|
75
|
+
customText.push(relPathToRegex(path));
|
|
76
|
+
if (['-text', 'binary'].includes(type))
|
|
77
|
+
customBinary.push(relPathToRegex(path));
|
|
78
|
+
}
|
|
68
79
|
// Custom vendor options
|
|
69
80
|
const vendorMatches = attributesData.matchAll(/^(\S+).*[^-]linguist-(vendored|generated|documentation)(?!=false)/gm);
|
|
70
81
|
for (const [_line, path] of vendorMatches) {
|
|
71
|
-
customIgnored.push(
|
|
82
|
+
customIgnored.push(relPathToRegex(path));
|
|
72
83
|
}
|
|
73
84
|
// Custom file associations
|
|
74
85
|
const customLangMatches = attributesData.matchAll(/^(\S+).*[^-]linguist-language=(\S+)/gm);
|
|
@@ -79,7 +90,7 @@ async function analyse(input, opts = {}) {
|
|
|
79
90
|
if (overrideLang)
|
|
80
91
|
forcedLang = overrideLang[0];
|
|
81
92
|
}
|
|
82
|
-
const fullPath = folder +
|
|
93
|
+
const fullPath = folder + (0, convert_glob_1.default)(path).source.substr(1);
|
|
83
94
|
overrides[fullPath] = forcedLang;
|
|
84
95
|
}
|
|
85
96
|
}
|
|
@@ -88,7 +99,7 @@ async function analyse(input, opts = {}) {
|
|
|
88
99
|
// Check vendored files
|
|
89
100
|
if (!opts.keepVendored) {
|
|
90
101
|
// Filter out any files that match a vendor file path
|
|
91
|
-
const matcher = (match) => (
|
|
102
|
+
const matcher = (match) => RegExp(match.replace(/\/$/, '/.+$').replace(/^\.\//, ''));
|
|
92
103
|
files = files.filter(file => !customIgnored.some(pattern => matcher(pattern).test(file)));
|
|
93
104
|
}
|
|
94
105
|
// Load all files and parse languages
|
|
@@ -151,8 +162,13 @@ async function analyse(input, opts = {}) {
|
|
|
151
162
|
// Narrow down file associations to the best fit
|
|
152
163
|
for (const file in fileAssociations) {
|
|
153
164
|
// Skip binary files
|
|
154
|
-
if (!opts.keepBinary
|
|
155
|
-
|
|
165
|
+
if (!opts.keepBinary) {
|
|
166
|
+
const isCustomText = customText.some(path => RegExp(path).test(file));
|
|
167
|
+
const isCustomBinary = customBinary.some(path => RegExp(path).test(file));
|
|
168
|
+
const isBinaryExt = binary_extensions_1.default.some(ext => file.endsWith('.' + ext));
|
|
169
|
+
if (!isCustomText && (isCustomBinary || isBinaryExt || await (0, isbinaryfile_1.isBinaryFile)(file))) {
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
156
172
|
}
|
|
157
173
|
// Parse heuristics if applicable
|
|
158
174
|
if (opts.checkHeuristics)
|
|
@@ -208,6 +224,17 @@ async function analyse(input, opts = {}) {
|
|
|
208
224
|
}
|
|
209
225
|
}
|
|
210
226
|
}
|
|
227
|
+
// Convert paths to relative
|
|
228
|
+
if (opts.relativePaths) {
|
|
229
|
+
const newMap = {};
|
|
230
|
+
for (const [file, lang] of Object.entries(results.files.results)) {
|
|
231
|
+
let relPath = path_1.default.relative(process.cwd(), file).replace(/\\/g, '/');
|
|
232
|
+
if (!relPath.startsWith('../'))
|
|
233
|
+
relPath = './' + relPath;
|
|
234
|
+
newMap[relPath] = lang;
|
|
235
|
+
}
|
|
236
|
+
results.files.results = newMap;
|
|
237
|
+
}
|
|
211
238
|
// Load language bytes size
|
|
212
239
|
for (const [file, lang] of Object.entries(results.files.results)) {
|
|
213
240
|
if (lang && !langData[lang])
|
package/dist/types.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "linguist-js",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
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": {
|
|
@@ -10,7 +10,6 @@
|
|
|
10
10
|
"node": ">=12"
|
|
11
11
|
},
|
|
12
12
|
"scripts": {
|
|
13
|
-
"prepublish": "tsc",
|
|
14
13
|
"prepare": "npm test && npm run perf",
|
|
15
14
|
"perf": "tsc && node test/perf",
|
|
16
15
|
"test": "tsc && node test/test"
|
|
@@ -38,9 +37,9 @@
|
|
|
38
37
|
"homepage": "https://github.com/Nixinova/Linguist#readme",
|
|
39
38
|
"dependencies": {
|
|
40
39
|
"binary-extensions": "^2.2.0",
|
|
41
|
-
"commander": "^8.
|
|
40
|
+
"commander": "^8.3.0",
|
|
42
41
|
"cross-fetch": "^3.1.4",
|
|
43
|
-
"glob-to-regexp": "
|
|
42
|
+
"glob-to-regexp": "~0.4.1",
|
|
44
43
|
"isbinaryfile": "^4.0.8",
|
|
45
44
|
"js-yaml": "^4.1.0",
|
|
46
45
|
"node-cache": "^5.1.2"
|
|
@@ -50,6 +49,6 @@
|
|
|
50
49
|
"@types/js-yaml": "ts4.4",
|
|
51
50
|
"@types/node": "ts4.4",
|
|
52
51
|
"deep-object-diff": "^1.1.0",
|
|
53
|
-
"typescript": "~4.4
|
|
52
|
+
"typescript": "~4.5.4"
|
|
54
53
|
}
|
|
55
54
|
}
|
package/readme.md
CHANGED
|
@@ -10,6 +10,7 @@ Powered by [github-linguist](https://github.com/github/linguist), although it do
|
|
|
10
10
|
|
|
11
11
|
## Install
|
|
12
12
|
|
|
13
|
+
[Node.js](https://nodejs.org) must be installed to be able to use this.
|
|
13
14
|
Linguist is available [on npm](https://npmjs.com/package/linguist-js) as `linguist-js`.
|
|
14
15
|
|
|
15
16
|
Install locally using `npm install linguist-js` and import it into your code like so:
|
|
@@ -76,8 +77,8 @@ Running Linguist on this folder will return the following JSON:
|
|
|
76
77
|
### Notes
|
|
77
78
|
|
|
78
79
|
- File paths in the output use only forward slashes as delimiters, even on Windows.
|
|
79
|
-
- Do not rely on any language classification output from Linguist being
|
|
80
|
-
Language data is fetched each run from the latest classifications of `github-linguist
|
|
80
|
+
- Do not rely on any language classification output from Linguist being unchanged between runs.
|
|
81
|
+
Language data is fetched each run from the latest classifications of [`github-linguist`](https://github.com/github/linguist).
|
|
81
82
|
This data is subject to change at any time and may change the results of a run even when using the same version of Linguist.
|
|
82
83
|
|
|
83
84
|
## API
|
|
@@ -114,6 +115,8 @@ const { files, languages, unknown } = linguist(folder, options);
|
|
|
114
115
|
Whether to keep vendored files (dependencies, etc) (defaults to `false`).
|
|
115
116
|
- `keepBinary` (boolean):
|
|
116
117
|
Whether binary files should be included in the output (defaults to `false`).
|
|
118
|
+
- `relativePaths` (boolean):
|
|
119
|
+
Change the absolute file paths in the output to be relative to the current working directory (defaults to `false`).
|
|
117
120
|
- `checkAttributes` (boolean):
|
|
118
121
|
Force the checking of `.gitattributes` files (defaults to `true` unless `quick` is set).
|
|
119
122
|
- `checkIgnored` (boolean):
|
|
@@ -132,35 +135,37 @@ linguist --help
|
|
|
132
135
|
|
|
133
136
|
- `--analyze`:
|
|
134
137
|
Analyse the language of all files found in a folder.
|
|
135
|
-
- `<folders
|
|
138
|
+
- `<folders...>`:
|
|
136
139
|
The folders to analyse (defaults to `./`).
|
|
137
|
-
- `--ignoredFiles <paths
|
|
140
|
+
- `--ignoredFiles <paths...>`:
|
|
138
141
|
A list of space-delimited file path globs to ignore.
|
|
139
|
-
- `--ignoredLanguages
|
|
142
|
+
- `--ignoredLanguages`:
|
|
140
143
|
A list of languages to ignore.
|
|
141
|
-
- `--categories <categories
|
|
144
|
+
- `--categories <categories...>`:
|
|
142
145
|
A list of space-delimited categories that should be displayed in the output.
|
|
143
|
-
- `--childLanguages
|
|
146
|
+
- `--childLanguages`:
|
|
144
147
|
Whether to display sub-languages instead of their parents, when possible.
|
|
145
|
-
- `--json
|
|
148
|
+
- `--json`:
|
|
146
149
|
Display the outputted language data as JSON.
|
|
147
|
-
- `--tree <traversal
|
|
150
|
+
- `--tree <traversal>`:
|
|
148
151
|
A dot-delimited traversal to the nested object that should be logged to the console instead of the entire output.
|
|
149
152
|
Requires `--json` to be specified.
|
|
150
|
-
- `--quick
|
|
153
|
+
- `--quick`:
|
|
151
154
|
Whether to skip the checking of `.gitattributes` and `.gitignore` files for manual language classifications.
|
|
152
155
|
Alias for `--checkAttributes=false --checkIgnored=false --checkHeuristics=false --checkShebang=false`.
|
|
153
|
-
- `--keepVendored
|
|
156
|
+
- `--keepVendored`:
|
|
154
157
|
Whether to include vendored files (auto-generated files, dependencies folder, etc).
|
|
155
|
-
- `--keepBinary
|
|
158
|
+
- `--keepBinary`:
|
|
156
159
|
Whether binary files should be excluded from the output.
|
|
157
|
-
- `--
|
|
160
|
+
- `--relativePaths`:
|
|
161
|
+
Change the absolute file paths in the output to be relative to the current working directory.
|
|
162
|
+
- `--checkAttributes`:
|
|
158
163
|
Force the checking of `.gitatributes` files (use alongside `--quick` to overwrite).
|
|
159
|
-
- `--checkIgnored
|
|
164
|
+
- `--checkIgnored`:
|
|
160
165
|
Force the checking of `.gitignore` files (use alongside `--quick` to overwrite).
|
|
161
|
-
- `--checkHeuristics
|
|
166
|
+
- `--checkHeuristics`:
|
|
162
167
|
Apply heuristics to ambiguous languages (use alongside `--quick` to overwrite).
|
|
163
|
-
- `--checkShebang
|
|
168
|
+
- `--checkShebang`:
|
|
164
169
|
Check shebang (`#!`) lines for explicit classification (use alongside `--quick` to overwrite).
|
|
165
170
|
- `--help`:
|
|
166
171
|
Display a help message.
|