linguist-js 2.2.1 → 2.4.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 +2 -4
- package/dist/helpers/convert-pcre.js +7 -4
- package/dist/helpers/walk-tree.js +37 -15
- package/dist/index.js +96 -34
- package/dist/types.d.ts +2 -0
- package/package.json +6 -5
- package/readme.md +12 -2
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
|
|
@@ -27,6 +26,7 @@ commander_1.program
|
|
|
27
26
|
.option('-I|--checkIgnored [bool]', 'Force the checking of gitignore files', true)
|
|
28
27
|
.option('-H|--checkHeuristics [bool]', 'Apply heuristics to ambiguous languages', true)
|
|
29
28
|
.option('-S|--checkShebang [bool]', 'Check shebang lines for explicit classification', true)
|
|
29
|
+
.option('-M|--checkModeline [bool]', 'Check modelines for explicit classification', true)
|
|
30
30
|
.helpOption(`-h|--help`, 'Display this help message')
|
|
31
31
|
.version(VERSION, '-v|--version', 'Display the installed version of linguist-js');
|
|
32
32
|
commander_1.program.parse(process.argv);
|
|
@@ -53,13 +53,11 @@ if (args.analyze)
|
|
|
53
53
|
const root = args.analyze === true ? '.' : args.analyze;
|
|
54
54
|
const data = await (0, index_1.default)(root, args);
|
|
55
55
|
const { files, languages, unknown } = data;
|
|
56
|
-
// Get file count
|
|
57
|
-
let totalFiles = (0, walk_tree_1.default)(root).files.length;
|
|
58
56
|
// Print output
|
|
59
57
|
if (!args.json) {
|
|
60
58
|
const sortedEntries = Object.entries(languages.results).sort((a, b) => a[1].bytes < b[1].bytes ? +1 : -1);
|
|
61
59
|
const totalBytes = languages.bytes;
|
|
62
|
-
console.log(`\n Analysed ${files.bytes.toLocaleString()} B from ${
|
|
60
|
+
console.log(`\n Analysed ${files.bytes.toLocaleString()} B from ${files.count} files with linguist-js`);
|
|
63
61
|
console.log(`\n Language analysis results:`);
|
|
64
62
|
let count = 0;
|
|
65
63
|
if (sortedEntries.length === 0)
|
|
@@ -3,23 +3,26 @@ 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
|
+
const replace = (search, replace) => finalRegex = finalRegex.replace(search, replace);
|
|
6
7
|
const finalFlags = new Set();
|
|
7
8
|
// Convert inline flag declarations
|
|
8
9
|
const inlineMatches = regex.matchAll(/\?([a-z]):/g);
|
|
9
10
|
const startMatches = regex.matchAll(/\(\?([a-z]+)\)/g);
|
|
10
11
|
for (const [match, flags] of [...inlineMatches, ...startMatches]) {
|
|
11
|
-
|
|
12
|
+
replace(match, '');
|
|
12
13
|
[...flags].forEach(flag => finalFlags.add(flag));
|
|
13
14
|
}
|
|
14
|
-
// Remove invalid
|
|
15
|
-
|
|
15
|
+
// Remove invalid syntax
|
|
16
|
+
replace(/([*+]){2}/g, '$1');
|
|
17
|
+
replace(/\(\?>/g, '(?:');
|
|
16
18
|
// Remove start/end-of-file markers
|
|
17
19
|
if (/\\[AZ]/.test(finalRegex)) {
|
|
18
|
-
|
|
20
|
+
replace(/\\A/g, '^').replace(/\\Z/g, '$');
|
|
19
21
|
finalFlags.delete('m');
|
|
20
22
|
}
|
|
21
23
|
else
|
|
22
24
|
finalFlags.add('m');
|
|
25
|
+
// Return final regex
|
|
23
26
|
return RegExp(finalRegex, [...finalFlags].join(''));
|
|
24
27
|
}
|
|
25
28
|
exports.default = pcre;
|
|
@@ -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,21 @@ 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;
|
|
18
|
-
var
|
|
18
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s;
|
|
19
|
+
var _t, _u, _v;
|
|
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
|
|
19
24
|
const langData = await (0, load_data_1.default)('languages.yml').then(js_yaml_1.default.load);
|
|
20
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);
|
|
21
27
|
const heuristicsData = await (0, load_data_1.default)('heuristics.yml').then(js_yaml_1.default.load);
|
|
22
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 : []; });
|
|
23
|
-
vendorData
|
|
29
|
+
const vendorPaths = [...vendorData, ...docData, ...generatedData];
|
|
30
|
+
// Setup main variables
|
|
24
31
|
const fileAssociations = {};
|
|
32
|
+
const definiteness = {};
|
|
25
33
|
const extensions = {};
|
|
26
34
|
const overrides = {};
|
|
27
35
|
const results = {
|
|
@@ -29,16 +37,41 @@ async function analyse(input, opts = {}) {
|
|
|
29
37
|
languages: { count: 0, bytes: 0, results: {} },
|
|
30
38
|
unknown: { count: 0, bytes: 0, extensions: {}, filenames: {} },
|
|
31
39
|
};
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
40
|
+
// Prepare list of ignored files
|
|
41
|
+
const ignoredFiles = ['(^|\\/)\\.git\\/'];
|
|
42
|
+
if (!opts.keepVendored) {
|
|
43
|
+
ignoredFiles.push(...vendorPaths);
|
|
44
|
+
}
|
|
45
|
+
if (opts.ignoredFiles) {
|
|
46
|
+
ignoredFiles.push(...opts.ignoredFiles.map(path => (0, glob_to_regexp_1.default)('*' + path + '*', { extended: true }).source));
|
|
47
|
+
}
|
|
48
|
+
// Load file paths and folders
|
|
49
|
+
let files, folders;
|
|
50
|
+
if (useRawContent) {
|
|
51
|
+
// Uses raw file content
|
|
52
|
+
files = input;
|
|
53
|
+
folders = [''];
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
// Uses directory on disc
|
|
57
|
+
// Set a common root path so that vendor paths do not incorrectly match parent folders
|
|
58
|
+
const resolvedInput = input.map(path => path_1.default.resolve(path).replace(/\\/g, '/'));
|
|
59
|
+
const commonRoot = (input.length > 1 ? (0, common_path_prefix_1.default)(resolvedInput) : resolvedInput[0]).replace(/\/?$/, '');
|
|
60
|
+
const data = (0, walk_tree_1.default)(commonRoot, input, ignoredFiles);
|
|
61
|
+
files = data.files;
|
|
62
|
+
folders = data.folders;
|
|
63
|
+
}
|
|
38
64
|
// Apply aliases
|
|
39
|
-
opts = {
|
|
65
|
+
opts = {
|
|
66
|
+
checkIgnored: !opts.quick,
|
|
67
|
+
checkAttributes: !opts.quick,
|
|
68
|
+
checkHeuristics: !opts.quick,
|
|
69
|
+
checkShebang: !opts.quick,
|
|
70
|
+
checkModeline: !opts.quick,
|
|
71
|
+
...opts
|
|
72
|
+
};
|
|
40
73
|
// Ignore specific languages
|
|
41
|
-
for (const lang of (
|
|
74
|
+
for (const lang of (_b = opts.ignoredLanguages) !== null && _b !== void 0 ? _b : []) {
|
|
42
75
|
for (const key in langData) {
|
|
43
76
|
if (lang.toLowerCase() === key.toLowerCase()) {
|
|
44
77
|
delete langData[key];
|
|
@@ -50,7 +83,7 @@ async function analyse(input, opts = {}) {
|
|
|
50
83
|
const customIgnored = [];
|
|
51
84
|
const customBinary = [];
|
|
52
85
|
const customText = [];
|
|
53
|
-
if (!opts.
|
|
86
|
+
if (!useRawContent && opts.checkAttributes) {
|
|
54
87
|
for (const folder of folders) {
|
|
55
88
|
// Skip if folder is marked in gitattributes
|
|
56
89
|
if (customIgnored.some(path => (0, convert_glob_1.default)(path).test(folder)))
|
|
@@ -97,7 +130,7 @@ async function analyse(input, opts = {}) {
|
|
|
97
130
|
}
|
|
98
131
|
}
|
|
99
132
|
// Check vendored files
|
|
100
|
-
if (!opts.keepVendored) {
|
|
133
|
+
if (!useRawContent && !opts.keepVendored) {
|
|
101
134
|
// Filter out any files that match a vendor file path
|
|
102
135
|
const matcher = (match) => RegExp(match.replace(/\/$/, '/.+$').replace(/^\.\//, ''));
|
|
103
136
|
files = files.filter(file => !customIgnored.some(pattern => matcher(pattern).test(file)));
|
|
@@ -110,33 +143,56 @@ async function analyse(input, opts = {}) {
|
|
|
110
143
|
}
|
|
111
144
|
const parent = !opts.childLanguages && result && langData[result].group || false;
|
|
112
145
|
fileAssociations[file].push(parent || result);
|
|
113
|
-
extensions[file] = path_1.default.extname(file);
|
|
146
|
+
extensions[file] = path_1.default.extname(file).toLowerCase();
|
|
114
147
|
};
|
|
115
148
|
const overridesArray = Object.entries(overrides);
|
|
116
149
|
// List all languages that could be associated with a given file
|
|
117
150
|
for (const file of files) {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
151
|
+
let firstLine;
|
|
152
|
+
if (useRawContent) {
|
|
153
|
+
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;
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
if (!fs_1.default.existsSync(file) || fs_1.default.lstatSync(file).isDirectory())
|
|
157
|
+
continue;
|
|
158
|
+
firstLine = await (0, read_file_1.default)(file, true).catch(() => null);
|
|
159
|
+
}
|
|
121
160
|
// Skip if file is unreadable
|
|
122
161
|
if (firstLine === null)
|
|
123
162
|
continue;
|
|
124
|
-
// Check
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
163
|
+
// Check first line for explicit classification
|
|
164
|
+
const hasShebang = opts.checkShebang && /^#!/.test(firstLine);
|
|
165
|
+
const hasModeline = opts.checkModeline && /-\*-|(syntax|filetype|ft)\s*=/.test(firstLine);
|
|
166
|
+
if (!opts.quick && (hasShebang || hasModeline)) {
|
|
167
|
+
const matches = [];
|
|
168
|
+
for (const [lang, data] of Object.entries(langData)) {
|
|
169
|
+
const langMatcher = (lang) => `\\b${lang.toLowerCase().replace(/\W/g, '\\$&')}\\b`;
|
|
170
|
+
// Check for interpreter match
|
|
171
|
+
const matchesInterpretor = (_f = data.interpreters) === null || _f === void 0 ? void 0 : _f.some(interpreter => firstLine.match(`\\b${interpreter}\\b`));
|
|
172
|
+
// Check modeline declaration
|
|
173
|
+
const matchesLang = firstLine.toLowerCase().match(langMatcher(lang));
|
|
174
|
+
const matchesAlias = (_g = data.aliases) === null || _g === void 0 ? void 0 : _g.some(lang => firstLine.toLowerCase().match(langMatcher(lang)));
|
|
175
|
+
// Add language
|
|
176
|
+
if (opts.checkShebang && matchesInterpretor)
|
|
177
|
+
matches.push(lang);
|
|
178
|
+
if (opts.checkModeline && (matchesLang || matchesAlias))
|
|
179
|
+
matches.push(lang);
|
|
180
|
+
}
|
|
128
181
|
if (matches.length) {
|
|
129
182
|
// Add explicitly-identified language
|
|
130
|
-
const forcedLang = matches[0]
|
|
183
|
+
const forcedLang = matches[0];
|
|
131
184
|
addResult(file, forcedLang);
|
|
185
|
+
definiteness[file] = true;
|
|
186
|
+
continue;
|
|
132
187
|
}
|
|
133
188
|
}
|
|
134
189
|
// Check override for manual language classification
|
|
135
|
-
if (!opts.quick && opts.checkAttributes) {
|
|
190
|
+
if (!useRawContent && !opts.quick && opts.checkAttributes) {
|
|
136
191
|
const match = overridesArray.find(item => RegExp(item[0]).test(file));
|
|
137
192
|
if (match) {
|
|
138
193
|
const forcedLang = match[1];
|
|
139
194
|
addResult(file, forcedLang);
|
|
195
|
+
definiteness[file] = true;
|
|
140
196
|
continue;
|
|
141
197
|
}
|
|
142
198
|
}
|
|
@@ -144,7 +200,7 @@ async function analyse(input, opts = {}) {
|
|
|
144
200
|
let skipExts = false;
|
|
145
201
|
for (const lang in langData) {
|
|
146
202
|
// Check if filename is a match
|
|
147
|
-
const matchesName = (
|
|
203
|
+
const matchesName = (_h = langData[lang].filenames) === null || _h === void 0 ? void 0 : _h.some(name => path_1.default.basename(file.toLowerCase()) === name.toLowerCase());
|
|
148
204
|
if (matchesName) {
|
|
149
205
|
addResult(file, lang);
|
|
150
206
|
skipExts = true;
|
|
@@ -153,7 +209,7 @@ async function analyse(input, opts = {}) {
|
|
|
153
209
|
if (!skipExts)
|
|
154
210
|
for (const lang in langData) {
|
|
155
211
|
// Check if extension is a match
|
|
156
|
-
const matchesExt = (
|
|
212
|
+
const matchesExt = (_j = langData[lang].extensions) === null || _j === void 0 ? void 0 : _j.some(ext => file.toLowerCase().endsWith(ext.toLowerCase()));
|
|
157
213
|
if (matchesExt)
|
|
158
214
|
addResult(file, lang);
|
|
159
215
|
}
|
|
@@ -163,8 +219,13 @@ async function analyse(input, opts = {}) {
|
|
|
163
219
|
}
|
|
164
220
|
// Narrow down file associations to the best fit
|
|
165
221
|
for (const file in fileAssociations) {
|
|
222
|
+
// Skip if file has explicit association
|
|
223
|
+
if (definiteness[file]) {
|
|
224
|
+
results.files.results[file] = fileAssociations[file][0];
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
166
227
|
// Skip binary files
|
|
167
|
-
if (!opts.keepBinary) {
|
|
228
|
+
if (!useRawContent && !opts.keepBinary) {
|
|
168
229
|
const isCustomText = customText.some(path => RegExp(path).test(file));
|
|
169
230
|
const isCustomBinary = customBinary.some(path => RegExp(path).test(file));
|
|
170
231
|
const isBinaryExt = binary_extensions_1.default.some(ext => file.endsWith('.' + ext));
|
|
@@ -186,13 +247,14 @@ async function analyse(input, opts = {}) {
|
|
|
186
247
|
heuristic.language = heuristic.language[0];
|
|
187
248
|
}
|
|
188
249
|
// Make sure the results includes this language
|
|
250
|
+
const languageGroup = (_k = langData[heuristic.language]) === null || _k === void 0 ? void 0 : _k.group;
|
|
189
251
|
const matchesLang = fileAssociations[file].includes(heuristic.language);
|
|
190
|
-
const matchesParent =
|
|
252
|
+
const matchesParent = languageGroup && fileAssociations[file].includes(languageGroup);
|
|
191
253
|
if (!matchesLang && !matchesParent)
|
|
192
254
|
continue;
|
|
193
255
|
// Normalise heuristic data
|
|
194
256
|
const patterns = [];
|
|
195
|
-
const normalise = (contents) => patterns.push(...
|
|
257
|
+
const normalise = (contents) => patterns.push(...[contents].flat());
|
|
196
258
|
if (heuristic.pattern)
|
|
197
259
|
normalise(heuristic.pattern);
|
|
198
260
|
if (heuristic.named_pattern)
|
|
@@ -208,10 +270,10 @@ async function analyse(input, opts = {}) {
|
|
|
208
270
|
}
|
|
209
271
|
}
|
|
210
272
|
// If no heuristics, assign a language
|
|
211
|
-
(
|
|
273
|
+
(_l = (_t = results.files.results)[file]) !== null && _l !== void 0 ? _l : (_t[file] = fileAssociations[file][0]);
|
|
212
274
|
}
|
|
213
275
|
// Skip specified categories
|
|
214
|
-
if ((
|
|
276
|
+
if ((_m = opts.categories) === null || _m === void 0 ? void 0 : _m.length) {
|
|
215
277
|
const categories = ['data', 'markup', 'programming', 'prose'];
|
|
216
278
|
const hiddenCategories = categories.filter(cat => !opts.categories.includes(cat));
|
|
217
279
|
for (const [file, lang] of Object.entries(results.files.results)) {
|
|
@@ -229,7 +291,7 @@ async function analyse(input, opts = {}) {
|
|
|
229
291
|
}
|
|
230
292
|
}
|
|
231
293
|
// Convert paths to relative
|
|
232
|
-
if (opts.relativePaths) {
|
|
294
|
+
if (!useRawContent && opts.relativePaths) {
|
|
233
295
|
const newMap = {};
|
|
234
296
|
for (const [file, lang] of Object.entries(results.files.results)) {
|
|
235
297
|
let relPath = path_1.default.relative(process.cwd(), file).replace(/\\/g, '/');
|
|
@@ -243,21 +305,21 @@ async function analyse(input, opts = {}) {
|
|
|
243
305
|
for (const [file, lang] of Object.entries(results.files.results)) {
|
|
244
306
|
if (lang && !langData[lang])
|
|
245
307
|
continue;
|
|
246
|
-
const fileSize = fs_1.default.statSync(file).size;
|
|
308
|
+
const fileSize = (_q = (_p = (_o = opts.fileContent) === null || _o === void 0 ? void 0 : _o[files.indexOf(file)]) === null || _p === void 0 ? void 0 : _p.length) !== null && _q !== void 0 ? _q : fs_1.default.statSync(file).size;
|
|
247
309
|
results.files.bytes += fileSize;
|
|
248
310
|
// If no language found, add extension in other section
|
|
249
311
|
if (!lang) {
|
|
250
312
|
const ext = path_1.default.extname(file);
|
|
251
313
|
const unknownType = ext === '' ? 'filenames' : 'extensions';
|
|
252
314
|
const name = ext === '' ? path_1.default.basename(file) : ext;
|
|
253
|
-
(
|
|
315
|
+
(_r = (_u = results.unknown[unknownType])[name]) !== null && _r !== void 0 ? _r : (_u[name] = 0);
|
|
254
316
|
results.unknown[unknownType][name] += fileSize;
|
|
255
317
|
results.unknown.bytes += fileSize;
|
|
256
318
|
continue;
|
|
257
319
|
}
|
|
258
320
|
// Add language and bytes data to corresponding section
|
|
259
321
|
const { type } = langData[lang];
|
|
260
|
-
(
|
|
322
|
+
(_s = (_v = results.languages.results)[lang]) !== null && _s !== void 0 ? _s : (_v[lang] = { type, bytes: 0, color: langData[lang].color });
|
|
261
323
|
if (opts.childLanguages)
|
|
262
324
|
results.languages.results[lang].parent = langData[lang].group;
|
|
263
325
|
results.languages.results[lang].bytes += fileSize;
|
package/dist/types.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export declare type FilePath = string;
|
|
|
5
5
|
export declare type Bytes = Integer;
|
|
6
6
|
export declare type Integer = number;
|
|
7
7
|
export interface Options {
|
|
8
|
+
fileContent?: string | string[];
|
|
8
9
|
ignoredFiles?: string[];
|
|
9
10
|
ignoredLanguages?: Language[];
|
|
10
11
|
categories?: Category[];
|
|
@@ -17,6 +18,7 @@ export interface Options {
|
|
|
17
18
|
checkAttributes?: boolean;
|
|
18
19
|
checkHeuristics?: boolean;
|
|
19
20
|
checkShebang?: boolean;
|
|
21
|
+
checkModeline?: boolean;
|
|
20
22
|
}
|
|
21
23
|
export interface Results {
|
|
22
24
|
files: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "linguist-js",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.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": {
|
|
@@ -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",
|
|
@@ -45,10 +46,10 @@
|
|
|
45
46
|
"node-cache": "^5.1.2"
|
|
46
47
|
},
|
|
47
48
|
"devDependencies": {
|
|
48
|
-
"@types/glob-to-regexp": "ts4.
|
|
49
|
-
"@types/js-yaml": "ts4.
|
|
50
|
-
"@types/node": "ts4.
|
|
49
|
+
"@types/glob-to-regexp": "ts4.6",
|
|
50
|
+
"@types/js-yaml": "ts4.6",
|
|
51
|
+
"@types/node": "ts4.6",
|
|
51
52
|
"deep-object-diff": "^1.1.7",
|
|
52
|
-
"typescript": "~4.
|
|
53
|
+
"typescript": "~4.6.1-rc"
|
|
53
54
|
}
|
|
54
55
|
}
|
package/readme.md
CHANGED
|
@@ -77,6 +77,7 @@ Running Linguist on this folder will return the following JSON:
|
|
|
77
77
|
### Notes
|
|
78
78
|
|
|
79
79
|
- File paths in the output use only forward slashes as delimiters, even on Windows.
|
|
80
|
+
- This tool does not work when offline.
|
|
80
81
|
- Do not rely on any language classification output from Linguist being unchanged between runs.
|
|
81
82
|
Language data is fetched each run from the latest classifications of [`github-linguist`](https://github.com/github/linguist).
|
|
82
83
|
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.
|
|
@@ -99,6 +100,8 @@ const { files, languages, unknown } = linguist(folder, options);
|
|
|
99
100
|
Analyse multiple folders using the syntax `"{folder1,folder2,...}"`.
|
|
100
101
|
- `opts` (optional; object):
|
|
101
102
|
An object containing analyser options.
|
|
103
|
+
- `fileContent` (string or string array):
|
|
104
|
+
Provides the file content associated with the file name(s) given as `entry` to analyse instead of reading from a folder on disk.
|
|
102
105
|
- `ignoredFiles` (string array):
|
|
103
106
|
A list of file path globs to explicitly ignore.
|
|
104
107
|
- `ignoredLanguages` (string array):
|
|
@@ -110,21 +113,26 @@ const { files, languages, unknown } = linguist(folder, options);
|
|
|
110
113
|
Whether to display sub-languages instead of their parents when possible (defaults to `false`).
|
|
111
114
|
- `quick` (boolean):
|
|
112
115
|
Whether to skip complex language analysis such as the checking of heuristics and gitattributes statements (defaults to `false`).
|
|
113
|
-
Alias for `checkAttributes:false, checkIgnored:false, checkHeuristics:false, checkShebang:false`.
|
|
116
|
+
Alias for `checkAttributes:false, checkIgnored:false, checkHeuristics:false, checkShebang:false, checkModeline:false`.
|
|
114
117
|
- `keepVendored` (boolean):
|
|
115
118
|
Whether to keep vendored files (dependencies, etc) (defaults to `false`).
|
|
119
|
+
Does nothing when `fileContent` is set.
|
|
116
120
|
- `keepBinary` (boolean):
|
|
117
121
|
Whether binary files should be included in the output (defaults to `false`).
|
|
118
122
|
- `relativePaths` (boolean):
|
|
119
123
|
Change the absolute file paths in the output to be relative to the current working directory (defaults to `false`).
|
|
120
124
|
- `checkAttributes` (boolean):
|
|
121
125
|
Force the checking of `.gitattributes` files (defaults to `true` unless `quick` is set).
|
|
126
|
+
Does nothing when `fileContent` is set.
|
|
122
127
|
- `checkIgnored` (boolean):
|
|
123
128
|
Force the checking of `.gitignore` files (defaults to `true` unless `quick` is set).
|
|
129
|
+
Does nothing when `fileContent` is set.
|
|
124
130
|
- `checkHeuristics` (boolean):
|
|
125
131
|
Apply heuristics to ambiguous languages (defaults to `true` unless `quick` is set).
|
|
126
132
|
- `checkShebang` (boolean):
|
|
127
133
|
Check shebang (`#!`) lines for explicit language classification (defaults to `true` unless `quick` is set).
|
|
134
|
+
- `checkModeline` (boolean):
|
|
135
|
+
Check modelines for explicit language classification (defaults to `true` unless `quick` is set).
|
|
128
136
|
|
|
129
137
|
### Command-line
|
|
130
138
|
|
|
@@ -152,7 +160,7 @@ linguist --help
|
|
|
152
160
|
Requires `--json` to be specified.
|
|
153
161
|
- `--quick`:
|
|
154
162
|
Whether to skip the checking of `.gitattributes` and `.gitignore` files for manual language classifications.
|
|
155
|
-
Alias for `--checkAttributes=false --checkIgnored=false --checkHeuristics=false --
|
|
163
|
+
Alias for `--checkAttributes=false --checkIgnored=false --checkHeuristics=false --checkExplicit=false --checkModeline=false`.
|
|
156
164
|
- `--keepVendored`:
|
|
157
165
|
Whether to include vendored files (auto-generated files, dependencies folder, etc).
|
|
158
166
|
- `--keepBinary`:
|
|
@@ -167,6 +175,8 @@ linguist --help
|
|
|
167
175
|
Apply heuristics to ambiguous languages (use alongside `--quick` to overwrite).
|
|
168
176
|
- `--checkShebang`:
|
|
169
177
|
Check shebang (`#!`) lines for explicit classification (use alongside `--quick` to overwrite).
|
|
178
|
+
- `--checkModeline`:
|
|
179
|
+
Check modelines for explicit classification (use alongside `--quick` to overwrite).
|
|
170
180
|
- `--help`:
|
|
171
181
|
Display a help message.
|
|
172
182
|
- `--version`:
|