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 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 { files, languages, unknown } = await (0, index_1.default)(root, args);
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(`Analysed ${files.bytes.toLocaleString()} B from ${files.count} files with linguist-js`);
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 i = 0;
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: (++i).toString().padStart(2, ' '),
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 : '#eee'), '\u2588'),
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(` '.${ext}': ${bytes.toLocaleString()} B`);
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 data = { files, languages, unknown };
84
- if (args.tree) {
85
- const treeParts = args.tree.split('.');
86
- let nestedData = data;
87
- for (const part of treeParts) {
88
- if (!nestedData[part])
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
- let finalFlags = new Set();
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 convertToRegex = (path) => (0, glob_to_regexp_1.default)('**/' + path, { globstar: true, extended: true });
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 => (0, convert_pcre_1.default)(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, convert_pcre_1.default)(path).test(folder)))
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 => convertToRegex(path).source);
62
- customIgnored.push(...ignoredPaths.map(file => file.replace(folder, '')));
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(convertToRegex(path).source.substr(1).replace(folder, ''));
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 + convertToRegex(path).source.substr(1);
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) => (0, convert_pcre_1.default)(match.replace(/\/$/, '/.+$').replace(/^\.\//, ''));
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 && (binary_extensions_1.default.some(ext => file.endsWith('.' + ext)) || await (0, isbinaryfile_1.isBinaryFile)(file))) {
155
- continue;
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
@@ -10,6 +10,7 @@ export interface Options {
10
10
  categories?: Category[];
11
11
  keepVendored?: boolean;
12
12
  keepBinary?: boolean;
13
+ relativePaths?: boolean;
13
14
  childLanguages?: boolean;
14
15
  quick?: boolean;
15
16
  checkIgnored?: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "linguist-js",
3
- "version": "2.1.1",
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.2.0",
40
+ "commander": "^8.3.0",
42
41
  "cross-fetch": "^3.1.4",
43
- "glob-to-regexp": "^0.4.1",
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.3"
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 immutable between runs.
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...>` (optional):
138
+ - `<folders...>`:
136
139
  The folders to analyse (defaults to `./`).
137
- - `--ignoredFiles <paths...>` (optional):
140
+ - `--ignoredFiles <paths...>`:
138
141
  A list of space-delimited file path globs to ignore.
139
- - `--ignoredLanguages` (optional):
142
+ - `--ignoredLanguages`:
140
143
  A list of languages to ignore.
141
- - `--categories <categories...>` (optional):
144
+ - `--categories <categories...>`:
142
145
  A list of space-delimited categories that should be displayed in the output.
143
- - `--childLanguages` (optional):
146
+ - `--childLanguages`:
144
147
  Whether to display sub-languages instead of their parents, when possible.
145
- - `--json` (optional):
148
+ - `--json`:
146
149
  Display the outputted language data as JSON.
147
- - `--tree <traversal>` (optional):
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` (optional):
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` (optional):
156
+ - `--keepVendored`:
154
157
  Whether to include vendored files (auto-generated files, dependencies folder, etc).
155
- - `--keepBinary` (optional):
158
+ - `--keepBinary`:
156
159
  Whether binary files should be excluded from the output.
157
- - `--checkAttributes` (optional):
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` (optional):
164
+ - `--checkIgnored`:
160
165
  Force the checking of `.gitignore` files (use alongside `--quick` to overwrite).
161
- - `--checkHeuristics` (optional):
166
+ - `--checkHeuristics`:
162
167
  Apply heuristics to ambiguous languages (use alongside `--quick` to overwrite).
163
- - `--checkShebang` (optional):
168
+ - `--checkShebang`:
164
169
  Check shebang (`#!`) lines for explicit classification (use alongside `--quick` to overwrite).
165
170
  - `--help`:
166
171
  Display a help message.