linguist-js 2.4.2 → 2.5.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 -0
- package/dist/helpers/load-data.js +11 -2
- package/dist/index.js +83 -46
- 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 +10 -8
- package/readme.md +6 -3
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;
|
package/dist/index.js
CHANGED
|
@@ -14,21 +14,20 @@ 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 = {};
|
|
31
|
-
const definiteness = {};
|
|
32
31
|
const extensions = {};
|
|
33
32
|
const overrides = {};
|
|
34
33
|
const results = {
|
|
@@ -86,8 +85,9 @@ async function analyse(input, opts = {}) {
|
|
|
86
85
|
if (!useRawContent && opts.checkAttributes) {
|
|
87
86
|
for (const folder of folders) {
|
|
88
87
|
// Skip if folder is marked in gitattributes
|
|
89
|
-
if (relPath(folder) && gitignores.ignores(relPath(folder)))
|
|
88
|
+
if (relPath(folder) && gitignores.ignores(relPath(folder))) {
|
|
90
89
|
continue;
|
|
90
|
+
}
|
|
91
91
|
// Parse gitignores
|
|
92
92
|
const ignoresFile = path_1.default.join(folder, '.gitignore');
|
|
93
93
|
if (opts.checkIgnored && fs_1.default.existsSync(ignoresFile)) {
|
|
@@ -101,10 +101,12 @@ async function analyse(input, opts = {}) {
|
|
|
101
101
|
// Explicit text/binary associations
|
|
102
102
|
const contentTypeMatches = attributesData.matchAll(/^(\S+).*?(-?binary|-?text)(?!=auto)/gm);
|
|
103
103
|
for (const [_line, path, type] of contentTypeMatches) {
|
|
104
|
-
if (['text', '-binary'].includes(type))
|
|
104
|
+
if (['text', '-binary'].includes(type)) {
|
|
105
105
|
customText.add(path);
|
|
106
|
-
|
|
106
|
+
}
|
|
107
|
+
if (['-text', 'binary'].includes(type)) {
|
|
107
108
|
customBinary.add(path);
|
|
109
|
+
}
|
|
108
110
|
}
|
|
109
111
|
// Custom vendor options
|
|
110
112
|
const vendorMatches = attributesData.matchAll(/^(\S+).*[^-]linguist-(vendored|generated|documentation)(?!=false)/gm);
|
|
@@ -117,8 +119,9 @@ async function analyse(input, opts = {}) {
|
|
|
117
119
|
// If specified language is an alias, associate it with its full name
|
|
118
120
|
if (!langData[forcedLang]) {
|
|
119
121
|
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)
|
|
122
|
+
if (overrideLang) {
|
|
121
123
|
forcedLang = overrideLang[0];
|
|
124
|
+
}
|
|
122
125
|
}
|
|
123
126
|
const fullPath = relPath(folder) + '/' + path;
|
|
124
127
|
overrides[fullPath] = forcedLang;
|
|
@@ -149,16 +152,18 @@ async function analyse(input, opts = {}) {
|
|
|
149
152
|
};
|
|
150
153
|
const overridesArray = Object.entries(overrides);
|
|
151
154
|
// List all languages that could be associated with a given file
|
|
155
|
+
const definiteness = {};
|
|
156
|
+
const fromShebang = {};
|
|
152
157
|
for (const file of files) {
|
|
153
158
|
let firstLine;
|
|
154
159
|
if (useRawContent) {
|
|
155
160
|
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
161
|
}
|
|
157
|
-
else {
|
|
158
|
-
if (!fs_1.default.existsSync(file) || fs_1.default.lstatSync(file).isDirectory())
|
|
159
|
-
continue;
|
|
162
|
+
else if (fs_1.default.existsSync(file) && !fs_1.default.lstatSync(file).isDirectory()) {
|
|
160
163
|
firstLine = await (0, read_file_1.default)(file, true).catch(() => null);
|
|
161
164
|
}
|
|
165
|
+
else
|
|
166
|
+
continue;
|
|
162
167
|
// Skip if file is unreadable
|
|
163
168
|
if (firstLine === null)
|
|
164
169
|
continue;
|
|
@@ -170,21 +175,27 @@ async function analyse(input, opts = {}) {
|
|
|
170
175
|
for (const [lang, data] of Object.entries(langData)) {
|
|
171
176
|
const langMatcher = (lang) => `\\b${lang.toLowerCase().replace(/\W/g, '\\$&')}(?![\\w#+*]|-\*-)`;
|
|
172
177
|
// Check for interpreter match
|
|
173
|
-
|
|
178
|
+
if (opts.checkShebang && hasShebang) {
|
|
179
|
+
const matchesInterpretor = (_f = data.interpreters) === null || _f === void 0 ? void 0 : _f.some(interpreter => firstLine.match(`\\b${interpreter}\\b`));
|
|
180
|
+
if (matchesInterpretor)
|
|
181
|
+
matches.push(lang);
|
|
182
|
+
}
|
|
174
183
|
// Check modeline declaration
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
184
|
+
if (opts.checkModeline && hasModeline) {
|
|
185
|
+
const modelineText = firstLine.toLowerCase().replace(/^.*-\*-(.+)-\*-.*$/, '$1');
|
|
186
|
+
const matchesLang = modelineText.match(langMatcher(lang));
|
|
187
|
+
const matchesAlias = (_g = data.aliases) === null || _g === void 0 ? void 0 : _g.some(lang => modelineText.match(langMatcher(lang)));
|
|
188
|
+
if (matchesLang || matchesAlias)
|
|
189
|
+
matches.push(lang);
|
|
190
|
+
}
|
|
182
191
|
}
|
|
192
|
+
// Add identified language(s)
|
|
183
193
|
if (matches.length) {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
194
|
+
for (const match of matches)
|
|
195
|
+
addResult(file, match);
|
|
196
|
+
if (matches.length === 1)
|
|
197
|
+
definiteness[file] = true;
|
|
198
|
+
fromShebang[file] = true;
|
|
188
199
|
continue;
|
|
189
200
|
}
|
|
190
201
|
}
|
|
@@ -201,24 +212,38 @@ async function analyse(input, opts = {}) {
|
|
|
201
212
|
}
|
|
202
213
|
// Search each language
|
|
203
214
|
let skipExts = false;
|
|
215
|
+
// Check if filename is a match
|
|
204
216
|
for (const lang in langData) {
|
|
205
|
-
// Check if filename is a match
|
|
206
217
|
const matchesName = (_h = langData[lang].filenames) === null || _h === void 0 ? void 0 : _h.some(name => path_1.default.basename(file.toLowerCase()) === name.toLowerCase());
|
|
207
218
|
if (matchesName) {
|
|
208
219
|
addResult(file, lang);
|
|
209
220
|
skipExts = true;
|
|
210
221
|
}
|
|
211
222
|
}
|
|
223
|
+
// Check if extension is a match
|
|
224
|
+
const possibleExts = [];
|
|
212
225
|
if (!skipExts)
|
|
213
226
|
for (const lang in langData) {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
227
|
+
const extMatches = (_j = langData[lang].extensions) === null || _j === void 0 ? void 0 : _j.filter(ext => file.toLowerCase().endsWith(ext.toLowerCase()));
|
|
228
|
+
if (extMatches === null || extMatches === void 0 ? void 0 : extMatches.length) {
|
|
229
|
+
for (const ext of extMatches)
|
|
230
|
+
possibleExts.push({ ext, lang });
|
|
231
|
+
}
|
|
218
232
|
}
|
|
233
|
+
// Apply more specific extension if available
|
|
234
|
+
const isComplexExt = (ext) => /\..+\./.test(ext);
|
|
235
|
+
const hasComplexExt = possibleExts.some(data => isComplexExt(data.ext));
|
|
236
|
+
for (const { ext, lang } of possibleExts) {
|
|
237
|
+
if (hasComplexExt && !isComplexExt(ext))
|
|
238
|
+
continue;
|
|
239
|
+
if (!hasComplexExt && isComplexExt(ext))
|
|
240
|
+
continue;
|
|
241
|
+
addResult(file, lang);
|
|
242
|
+
}
|
|
219
243
|
// Fallback to null if no language matches
|
|
220
|
-
if (!fileAssociations[file])
|
|
244
|
+
if (!fileAssociations[file]) {
|
|
221
245
|
addResult(file, null);
|
|
246
|
+
}
|
|
222
247
|
}
|
|
223
248
|
// Narrow down file associations to the best fit
|
|
224
249
|
for (const file in fileAssociations) {
|
|
@@ -240,9 +265,8 @@ async function analyse(input, opts = {}) {
|
|
|
240
265
|
if (opts.checkHeuristics)
|
|
241
266
|
for (const heuristics of heuristicsData.disambiguations) {
|
|
242
267
|
// Make sure the extension matches the current file
|
|
243
|
-
if (!heuristics.extensions.includes(extensions[file]))
|
|
268
|
+
if (!fromShebang[file] && !heuristics.extensions.includes(extensions[file]))
|
|
244
269
|
continue;
|
|
245
|
-
}
|
|
246
270
|
// Load heuristic rules
|
|
247
271
|
for (const heuristic of heuristics.rules) {
|
|
248
272
|
// Make sure the language is not an array
|
|
@@ -262,8 +286,16 @@ async function analyse(input, opts = {}) {
|
|
|
262
286
|
normalise(heuristic.pattern);
|
|
263
287
|
if (heuristic.named_pattern)
|
|
264
288
|
normalise(heuristicsData.named_patterns[heuristic.named_pattern]);
|
|
289
|
+
if (heuristic.and) {
|
|
290
|
+
for (const data of heuristic.and) {
|
|
291
|
+
if (data.pattern)
|
|
292
|
+
normalise(data.pattern);
|
|
293
|
+
if (data.named_pattern)
|
|
294
|
+
normalise(heuristicsData.named_patterns[data.named_pattern]);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
265
297
|
// Check file contents and apply heuristic patterns
|
|
266
|
-
const fileContent = opts.fileContent ? opts.fileContent[files.indexOf(file)] : await (0, read_file_1.default)(file).catch(() => null);
|
|
298
|
+
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
299
|
if (fileContent === null)
|
|
268
300
|
continue;
|
|
269
301
|
if (!patterns.length || patterns.some(pattern => (0, convert_pcre_1.default)(pattern).test(fileContent))) {
|
|
@@ -273,23 +305,26 @@ async function analyse(input, opts = {}) {
|
|
|
273
305
|
}
|
|
274
306
|
}
|
|
275
307
|
// If no heuristics, assign a language
|
|
276
|
-
(
|
|
308
|
+
(_m = (_u = results.files.results)[file]) !== null && _m !== void 0 ? _m : (_u[file] = fileAssociations[file][0]);
|
|
277
309
|
}
|
|
278
310
|
// Skip specified categories
|
|
279
|
-
if ((
|
|
311
|
+
if ((_o = opts.categories) === null || _o === void 0 ? void 0 : _o.length) {
|
|
280
312
|
const categories = ['data', 'markup', 'programming', 'prose'];
|
|
281
313
|
const hiddenCategories = categories.filter(cat => !opts.categories.includes(cat));
|
|
282
314
|
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; }))
|
|
315
|
+
if (!hiddenCategories.some(cat => { var _a; return lang && ((_a = langData[lang]) === null || _a === void 0 ? void 0 : _a.type) === cat; })) {
|
|
284
316
|
continue;
|
|
317
|
+
}
|
|
285
318
|
delete results.files.results[file];
|
|
286
|
-
if (lang)
|
|
319
|
+
if (lang) {
|
|
287
320
|
delete results.languages.results[lang];
|
|
321
|
+
}
|
|
288
322
|
}
|
|
289
323
|
for (const category of hiddenCategories) {
|
|
290
324
|
for (const [lang, { type }] of Object.entries(results.languages.results)) {
|
|
291
|
-
if (type === category)
|
|
325
|
+
if (type === category) {
|
|
292
326
|
delete results.languages.results[lang];
|
|
327
|
+
}
|
|
293
328
|
}
|
|
294
329
|
}
|
|
295
330
|
}
|
|
@@ -298,8 +333,9 @@ async function analyse(input, opts = {}) {
|
|
|
298
333
|
const newMap = {};
|
|
299
334
|
for (const [file, lang] of Object.entries(results.files.results)) {
|
|
300
335
|
let relPath = path_1.default.relative(process.cwd(), file).replace(/\\/g, '/');
|
|
301
|
-
if (!relPath.startsWith('../'))
|
|
336
|
+
if (!relPath.startsWith('../')) {
|
|
302
337
|
relPath = './' + relPath;
|
|
338
|
+
}
|
|
303
339
|
newMap[relPath] = lang;
|
|
304
340
|
}
|
|
305
341
|
results.files.results = newMap;
|
|
@@ -308,23 +344,24 @@ async function analyse(input, opts = {}) {
|
|
|
308
344
|
for (const [file, lang] of Object.entries(results.files.results)) {
|
|
309
345
|
if (lang && !langData[lang])
|
|
310
346
|
continue;
|
|
311
|
-
const fileSize = (
|
|
347
|
+
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
348
|
results.files.bytes += fileSize;
|
|
313
349
|
// If no language found, add extension in other section
|
|
314
350
|
if (!lang) {
|
|
315
351
|
const ext = path_1.default.extname(file);
|
|
316
352
|
const unknownType = ext === '' ? 'filenames' : 'extensions';
|
|
317
353
|
const name = ext === '' ? path_1.default.basename(file) : ext;
|
|
318
|
-
(
|
|
354
|
+
(_s = (_v = results.unknown[unknownType])[name]) !== null && _s !== void 0 ? _s : (_v[name] = 0);
|
|
319
355
|
results.unknown[unknownType][name] += fileSize;
|
|
320
356
|
results.unknown.bytes += fileSize;
|
|
321
357
|
continue;
|
|
322
358
|
}
|
|
323
359
|
// Add language and bytes data to corresponding section
|
|
324
360
|
const { type } = langData[lang];
|
|
325
|
-
(
|
|
326
|
-
if (opts.childLanguages)
|
|
361
|
+
(_t = (_w = results.languages.results)[lang]) !== null && _t !== void 0 ? _t : (_w[lang] = { type, bytes: 0, color: langData[lang].color });
|
|
362
|
+
if (opts.childLanguages) {
|
|
327
363
|
results.languages.results[lang].parent = langData[lang].group;
|
|
364
|
+
}
|
|
328
365
|
results.languages.results[lang].bytes += fileSize;
|
|
329
366
|
results.languages.bytes += fileSize;
|
|
330
367
|
}
|
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?/
|