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 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
- /** Load a data file from github-linguist. */
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
- const allFiles = new Set();
9
- const allFolders = new Set();
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 _t, _u, _v;
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.test(relPath(folder)).ignored)
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
- if (['-text', 'binary'].includes(type))
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
- if (opts.checkShebang && matchesInterpretor)
179
- matches.push(lang);
180
- if (opts.checkModeline && (matchesLang || matchesAlias))
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).test(relPath(file)).ignored;
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.test(relPath(file)).ignored;
233
- const isCustomBinary = customBinary.test(relPath(file)).ignored;
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
- (_l = (_t = results.files.results)[file]) !== null && _l !== void 0 ? _l : (_t[file] = fileAssociations[file][0]);
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 ((_m = opts.categories) === null || _m === void 0 ? void 0 : _m.length) {
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 = (_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;
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
- (_r = (_u = results.unknown[unknownType])[name]) !== null && _r !== void 0 ? _r : (_u[name] = 0);
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
- (_s = (_v = results.languages.results)[lang]) !== null && _s !== void 0 ? _s : (_v[lang] = { type, bytes: 0, color: langData[lang].color });
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
@@ -14,6 +14,7 @@ export interface Options {
14
14
  relativePaths?: boolean;
15
15
  childLanguages?: boolean;
16
16
  quick?: boolean;
17
+ offline?: boolean;
17
18
  checkIgnored?: boolean;
18
19
  checkAttributes?: boolean;
19
20
  checkHeuristics?: boolean;
@@ -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?/