linguist-js 2.8.0 → 2.8.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/helpers/convert-pcre.js +1 -1
- package/dist/helpers/load-data.d.ts +1 -1
- package/dist/helpers/load-data.js +4 -5
- package/dist/helpers/parse-gitattributes.js +1 -1
- package/dist/helpers/parse-gitignore.js +1 -1
- package/dist/helpers/read-file.js +1 -1
- package/dist/helpers/walk-tree.js +7 -2
- package/dist/index.js +1 -1
- package/dist/package.json +56 -0
- package/dist/src/cli.d.ts +1 -0
- package/dist/src/cli.js +146 -0
- package/dist/src/helpers/convert-pcre.d.ts +2 -0
- package/dist/src/helpers/convert-pcre.js +38 -0
- package/dist/src/helpers/load-data.d.ts +4 -0
- package/dist/src/helpers/load-data.js +37 -0
- package/dist/src/helpers/norm-path.d.ts +2 -0
- package/dist/src/helpers/norm-path.js +15 -0
- package/dist/src/helpers/parse-gitattributes.d.ts +17 -0
- package/dist/src/helpers/parse-gitattributes.js +37 -0
- package/dist/src/helpers/parse-gitignore.d.ts +1 -0
- package/dist/src/helpers/parse-gitignore.js +12 -0
- package/dist/src/helpers/read-file.d.ts +5 -0
- package/dist/src/helpers/read-file.js +23 -0
- package/dist/src/helpers/walk-tree.d.ts +20 -0
- package/dist/src/helpers/walk-tree.js +85 -0
- package/dist/src/index.d.ts +4 -0
- package/dist/src/index.js +485 -0
- package/dist/src/schema.d.ts +37 -0
- package/dist/src/schema.js +2 -0
- package/dist/src/types.d.ts +74 -0
- package/dist/src/types.js +2 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +4 -0
- package/ext/documentation.yml +0 -3
- package/ext/generated.rb +1 -0
- package/ext/heuristics.yml +53 -15
- package/ext/languages.yml +93 -4
- package/ext/vendor.yml +0 -2
- package/package.json +3 -2
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = pcre;
|
|
3
4
|
/** Convert a PCRE regex into JS. */
|
|
4
5
|
function pcre(regex) {
|
|
5
6
|
let finalRegex = regex;
|
|
@@ -35,4 +36,3 @@ function pcre(regex) {
|
|
|
35
36
|
// Return final regex
|
|
36
37
|
return RegExp(finalRegex, [...finalFlags].join(''));
|
|
37
38
|
}
|
|
38
|
-
exports.default = pcre;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
/** Nukes unused `generated.rb` file content. */
|
|
2
|
-
export declare function parseGeneratedDataFile(fileContent: string):
|
|
2
|
+
export declare function parseGeneratedDataFile(fileContent: string): string[];
|
|
3
3
|
/** Load a data file from github-linguist. */
|
|
4
4
|
export default function loadFile(file: string, offline?: boolean): Promise<string>;
|
|
@@ -3,7 +3,8 @@ 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
|
-
exports.parseGeneratedDataFile =
|
|
6
|
+
exports.parseGeneratedDataFile = parseGeneratedDataFile;
|
|
7
|
+
exports.default = loadFile;
|
|
7
8
|
const fs_1 = __importDefault(require("fs"));
|
|
8
9
|
const path_1 = __importDefault(require("path"));
|
|
9
10
|
const cross_fetch_1 = __importDefault(require("cross-fetch"));
|
|
@@ -26,13 +27,11 @@ async function loadLocalFile(file) {
|
|
|
26
27
|
return fs_1.default.promises.readFile(filePath).then(buffer => buffer.toString());
|
|
27
28
|
}
|
|
28
29
|
/** Nukes unused `generated.rb` file content. */
|
|
29
|
-
|
|
30
|
+
function parseGeneratedDataFile(fileContent) {
|
|
30
31
|
var _a;
|
|
31
32
|
return [...(_a = fileContent.match(/(?<=name\.match\(\/).+?(?=(?<!\\)\/)/gm)) !== null && _a !== void 0 ? _a : []];
|
|
32
33
|
}
|
|
33
|
-
exports.parseGeneratedDataFile = parseGeneratedDataFile;
|
|
34
34
|
/** Load a data file from github-linguist. */
|
|
35
|
-
|
|
35
|
+
function loadFile(file, offline = false) {
|
|
36
36
|
return offline ? loadLocalFile(file) : loadWebFile(file);
|
|
37
37
|
}
|
|
38
|
-
exports.default = loadFile;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = parseAttributes;
|
|
3
4
|
const norm_path_1 = require("./norm-path");
|
|
4
5
|
/**
|
|
5
6
|
* Parses a gitattributes file.
|
|
@@ -34,4 +35,3 @@ function parseAttributes(content, folderRoot = '.') {
|
|
|
34
35
|
}
|
|
35
36
|
return output;
|
|
36
37
|
}
|
|
37
|
-
exports.default = parseAttributes;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = parseGitignore;
|
|
3
4
|
function parseGitignore(content) {
|
|
4
5
|
const readableData = content
|
|
5
6
|
// Remove comments unless escaped
|
|
@@ -9,4 +10,3 @@ function parseGitignore(content) {
|
|
|
9
10
|
const arrayData = readableData.split(/\r?\n/).filter(data => data);
|
|
10
11
|
return arrayData;
|
|
11
12
|
}
|
|
12
|
-
exports.default = parseGitignore;
|
|
@@ -3,6 +3,7 @@ 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
|
+
exports.default = readFileChunk;
|
|
6
7
|
const fs_1 = __importDefault(require("fs"));
|
|
7
8
|
/**
|
|
8
9
|
* Read part of a file on disc.
|
|
@@ -20,4 +21,3 @@ async function readFileChunk(filename, onlyFirstLine = false) {
|
|
|
20
21
|
}
|
|
21
22
|
return content;
|
|
22
23
|
}
|
|
23
|
-
exports.default = readFileChunk;
|
|
@@ -3,6 +3,7 @@ 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
|
+
exports.default = walk;
|
|
6
7
|
const fs_1 = __importDefault(require("fs"));
|
|
7
8
|
const path_1 = __importDefault(require("path"));
|
|
8
9
|
const parse_gitignore_1 = __importDefault(require("./parse-gitignore"));
|
|
@@ -36,7 +37,12 @@ function walk(data) {
|
|
|
36
37
|
if (fs_1.default.existsSync(gitignoreFilename)) {
|
|
37
38
|
const gitignoreContents = fs_1.default.readFileSync(gitignoreFilename, 'utf-8');
|
|
38
39
|
const ignoredPaths = (0, parse_gitignore_1.default)(gitignoreContents);
|
|
39
|
-
|
|
40
|
+
const rootRelIgnoredPaths = ignoredPaths.map(ignorePath =>
|
|
41
|
+
// get absolute path of the ignore glob
|
|
42
|
+
(0, norm_path_1.normPath)(folder, ignorePath)
|
|
43
|
+
// convert abs ignore glob to be relative to the root folder
|
|
44
|
+
.replace(commonRoot + '/', ''));
|
|
45
|
+
ignored.add(rootRelIgnoredPaths);
|
|
40
46
|
}
|
|
41
47
|
// Add gitattributes if present
|
|
42
48
|
const gitattributesPath = (0, norm_path_1.normPath)(folder, '.gitattributes');
|
|
@@ -82,4 +88,3 @@ function walk(data) {
|
|
|
82
88
|
folders: [...allFolders],
|
|
83
89
|
};
|
|
84
90
|
}
|
|
85
|
-
exports.default = walk;
|
package/dist/index.js
CHANGED
|
@@ -46,7 +46,7 @@ async function analyse(rawPaths, opts = {}) {
|
|
|
46
46
|
const manualFileContent = [(_a = opts.fileContent) !== null && _a !== void 0 ? _a : []].flat();
|
|
47
47
|
// Normalise input option arguments
|
|
48
48
|
opts = {
|
|
49
|
-
calculateLines: (_b = opts.calculateLines) !== null && _b !== void 0 ? _b : true,
|
|
49
|
+
calculateLines: (_b = opts.calculateLines) !== null && _b !== void 0 ? _b : true, // default to true if unset
|
|
50
50
|
checkIgnored: !opts.quick,
|
|
51
51
|
checkDetected: !opts.quick,
|
|
52
52
|
checkAttributes: !opts.quick,
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "linguist-js",
|
|
3
|
+
"version": "2.8.0",
|
|
4
|
+
"description": "Analyse languages used in a folder. Powered by GitHub Linguist, although it doesn't need to be installed.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"linguist-js": "bin/index.js",
|
|
8
|
+
"linguist": "bin/index.js"
|
|
9
|
+
},
|
|
10
|
+
"engines": {
|
|
11
|
+
"node": ">=12"
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"download-files": "npx tsx@3 build/download-files",
|
|
15
|
+
"pre-publish": "npm run download-files && npm test && npm run perf",
|
|
16
|
+
"perf": "tsc && node test/perf",
|
|
17
|
+
"test": "tsc && node test/folder && node test/unit"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"bin/",
|
|
21
|
+
"dist/",
|
|
22
|
+
"ext/"
|
|
23
|
+
],
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/Nixinova/Linguist.git"
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"linguist",
|
|
30
|
+
"languages",
|
|
31
|
+
"language-analysis",
|
|
32
|
+
"language-analyzer"
|
|
33
|
+
],
|
|
34
|
+
"author": "Nixinova (https://nixinova.com)",
|
|
35
|
+
"license": "ISC",
|
|
36
|
+
"bugs": {
|
|
37
|
+
"url": "https://github.com/Nixinova/Linguist/issues"
|
|
38
|
+
},
|
|
39
|
+
"homepage": "https://github.com/Nixinova/Linguist#readme",
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"binary-extensions": "^2.3.0 <3",
|
|
42
|
+
"commander": "^9.5.0 <10",
|
|
43
|
+
"common-path-prefix": "^3.0.0",
|
|
44
|
+
"cross-fetch": "^3.1.8 <4",
|
|
45
|
+
"ignore": "^5.3.2",
|
|
46
|
+
"isbinaryfile": "^4.0.10 <5",
|
|
47
|
+
"js-yaml": "^4.1.0",
|
|
48
|
+
"node-cache": "^5.1.2"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@types/js-yaml": "^4.0.9",
|
|
52
|
+
"@types/node": "ts5.0",
|
|
53
|
+
"deep-object-diff": "^1.1.9",
|
|
54
|
+
"typescript": "~5.0.4 <5.1"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/src/cli.js
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
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 package_json_1 = __importDefault(require("../package.json"));
|
|
7
|
+
;
|
|
8
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const commander_1 = require("commander");
|
|
11
|
+
const index_1 = __importDefault(require("./index"));
|
|
12
|
+
const norm_path_1 = require("./helpers/norm-path");
|
|
13
|
+
const colouredMsg = ([r, g, b], msg) => `\u001B[${38};2;${r};${g};${b}m${msg}${'\u001b[0m'}`;
|
|
14
|
+
const hexToRgb = (hex) => [parseInt(hex.slice(1, 3), 16), parseInt(hex.slice(3, 5), 16), parseInt(hex.slice(5, 7), 16)];
|
|
15
|
+
commander_1.program
|
|
16
|
+
.name('linguist')
|
|
17
|
+
.usage('--analyze [<folders...>] [<options...>]')
|
|
18
|
+
.option('-a|--analyze|--analyse [folders...]', 'Analyse the languages of all files in a folder')
|
|
19
|
+
.option('-i|--ignoredFiles <files...>', `A list of file path globs to ignore`)
|
|
20
|
+
.option('-l|--ignoredLanguages <languages...>', `A list of languages to ignore`)
|
|
21
|
+
.option('-c|--categories <categories...>', 'Language categories to include in output')
|
|
22
|
+
.option('-C|--childLanguages [bool]', 'Display child languages instead of their parents', false)
|
|
23
|
+
.option('-j|--json [bool]', 'Display the output as JSON', false)
|
|
24
|
+
.option('-t|--tree <traversal>', 'Which part of the output JSON to display (dot-delimited)')
|
|
25
|
+
.option('-F|--listFiles [bool]', 'Whether to list every matching file under the language results', false)
|
|
26
|
+
.option('-q|--quick [bool]', 'Skip complex language analysis (alias for -{A|I|H|S}=false)', false)
|
|
27
|
+
.option('-o|--offline [bool]', 'Use packaged data files instead of fetching latest from GitHub', false)
|
|
28
|
+
.option('-L|--calculateLines [bool]', 'Calculate lines of code totals', true)
|
|
29
|
+
.option('-V|--keepVendored [bool]', 'Prevent skipping over vendored/generated files', false)
|
|
30
|
+
.option('-B|--keepBinary [bool]', 'Prevent skipping over binary files', false)
|
|
31
|
+
.option('-r|--relativePaths [bool]', 'Convert absolute file paths to relative', false)
|
|
32
|
+
.option('-A|--checkAttributes [bool]', 'Force the checking of gitattributes files', true)
|
|
33
|
+
.option('-I|--checkIgnored [bool]', 'Force the checking of gitignore files', true)
|
|
34
|
+
.option('-D|--checkDetected [bool]', 'Force files marked with linguist-detectable to always appear in output', true)
|
|
35
|
+
.option('-H|--checkHeuristics [bool]', 'Apply heuristics to ambiguous languages', true)
|
|
36
|
+
.option('-S|--checkShebang [bool]', 'Check shebang lines for explicit classification', true)
|
|
37
|
+
.option('-M|--checkModeline [bool]', 'Check modelines for explicit classification', true)
|
|
38
|
+
.helpOption(`-h|--help`, 'Display this help message')
|
|
39
|
+
.version(package_json_1.default.version, '-v|--version', 'Display the installed version of linguist-js');
|
|
40
|
+
commander_1.program.parse(process.argv);
|
|
41
|
+
const args = commander_1.program.opts();
|
|
42
|
+
// Normalise arguments
|
|
43
|
+
for (const arg in args) {
|
|
44
|
+
const normalise = (val) => {
|
|
45
|
+
if (typeof val !== 'string')
|
|
46
|
+
return val;
|
|
47
|
+
val = val.replace(/^=/, '');
|
|
48
|
+
if (val.match(/true$|false$/))
|
|
49
|
+
val = val === 'true';
|
|
50
|
+
return val;
|
|
51
|
+
};
|
|
52
|
+
if (Array.isArray(args[arg]))
|
|
53
|
+
args[arg] = args[arg].map(normalise);
|
|
54
|
+
else
|
|
55
|
+
args[arg] = normalise(args[arg]);
|
|
56
|
+
}
|
|
57
|
+
// Run Linguist
|
|
58
|
+
if (args.analyze)
|
|
59
|
+
(async () => {
|
|
60
|
+
// Fetch language data
|
|
61
|
+
const root = args.analyze === true ? '.' : args.analyze;
|
|
62
|
+
const data = await (0, index_1.default)(root, args);
|
|
63
|
+
const { files, languages, unknown } = data;
|
|
64
|
+
// Print output
|
|
65
|
+
if (!args.json) {
|
|
66
|
+
const sortedEntries = Object.entries(languages.results).sort((a, b) => a[1].bytes < b[1].bytes ? +1 : -1);
|
|
67
|
+
const totalBytes = languages.bytes;
|
|
68
|
+
console.log(`\n Analysed ${files.bytes.toLocaleString()} B from ${files.count} files with linguist-js`);
|
|
69
|
+
console.log(`\n Language analysis results: \n`);
|
|
70
|
+
let count = 0;
|
|
71
|
+
if (sortedEntries.length === 0)
|
|
72
|
+
console.log(` None`);
|
|
73
|
+
// Collate files per language
|
|
74
|
+
const filesPerLanguage = {};
|
|
75
|
+
if (args.listFiles) {
|
|
76
|
+
for (const language of Object.keys(languages.results)) {
|
|
77
|
+
filesPerLanguage[language] = [];
|
|
78
|
+
}
|
|
79
|
+
for (const [file, lang] of Object.entries(files.results)) {
|
|
80
|
+
if (lang)
|
|
81
|
+
filesPerLanguage[lang].push(file);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// List parsed results
|
|
85
|
+
for (const [lang, { bytes, lines, color }] of sortedEntries) {
|
|
86
|
+
const percent = (bytes) => bytes / (totalBytes || 1) * 100;
|
|
87
|
+
const fmtd = {
|
|
88
|
+
index: (++count).toString().padStart(2, ' '),
|
|
89
|
+
lang: lang.padEnd(24, ' '),
|
|
90
|
+
percent: percent(bytes).toFixed(2).padStart(5, ' '),
|
|
91
|
+
bytes: bytes.toLocaleString().padStart(10, ' '),
|
|
92
|
+
loc: lines.code.toLocaleString().padStart(10, ' '),
|
|
93
|
+
icon: colouredMsg(hexToRgb(color !== null && color !== void 0 ? color : '#ededed'), '\u2588'),
|
|
94
|
+
};
|
|
95
|
+
console.log(` ${fmtd.index}. ${fmtd.icon} ${fmtd.lang} ${fmtd.percent}% ${fmtd.bytes} B ${fmtd.loc} LOC`);
|
|
96
|
+
// If using `listFiles` option, list all files tagged as this language
|
|
97
|
+
if (args.listFiles) {
|
|
98
|
+
console.log(); // padding
|
|
99
|
+
for (const file of filesPerLanguage[lang]) {
|
|
100
|
+
let relFile = (0, norm_path_1.normPath)(node_path_1.default.relative(node_path_1.default.resolve('.'), file));
|
|
101
|
+
if (!relFile.startsWith('../'))
|
|
102
|
+
relFile = './' + relFile;
|
|
103
|
+
const bytes = (await node_fs_1.default.promises.stat(file)).size;
|
|
104
|
+
const fmtd2 = {
|
|
105
|
+
file: relFile.padEnd(42, ' '),
|
|
106
|
+
percent: percent(bytes).toFixed(2).padStart(5, ' '),
|
|
107
|
+
bytes: bytes.toLocaleString().padStart(10, ' '),
|
|
108
|
+
};
|
|
109
|
+
console.log(` ${fmtd.icon} ${fmtd2.file} ${fmtd2.percent}% ${fmtd2.bytes} B`);
|
|
110
|
+
}
|
|
111
|
+
console.log(); // padding
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (!args.listFiles)
|
|
115
|
+
console.log(); // padding
|
|
116
|
+
console.log(` Total: ${totalBytes.toLocaleString()} B`);
|
|
117
|
+
// List unknown files/extensions
|
|
118
|
+
if (unknown.bytes > 0) {
|
|
119
|
+
console.log(`\n Unknown files and extensions:`);
|
|
120
|
+
for (const [name, bytes] of Object.entries(unknown.filenames)) {
|
|
121
|
+
console.log(` '${name}': ${bytes.toLocaleString()} B`);
|
|
122
|
+
}
|
|
123
|
+
for (const [ext, bytes] of Object.entries(unknown.extensions)) {
|
|
124
|
+
console.log(` '*${ext}': ${bytes.toLocaleString()} B`);
|
|
125
|
+
}
|
|
126
|
+
console.log(` Total: ${unknown.bytes.toLocaleString()} B`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
else if (args.tree) {
|
|
130
|
+
const treeParts = args.tree.split('.');
|
|
131
|
+
let nestedData = data;
|
|
132
|
+
for (const part of treeParts) {
|
|
133
|
+
if (!nestedData[part])
|
|
134
|
+
throw Error(`TraversalError: Key '${part}' cannot be found on output object.`);
|
|
135
|
+
nestedData = nestedData[part];
|
|
136
|
+
}
|
|
137
|
+
console.log(nestedData);
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
console.dir(data, { depth: null });
|
|
141
|
+
}
|
|
142
|
+
})();
|
|
143
|
+
else {
|
|
144
|
+
console.log(`Welcome to linguist-js, a JavaScript port of GitHub's language analyzer.`);
|
|
145
|
+
console.log(`Type 'linguist --help' for a list of commands.`);
|
|
146
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = pcre;
|
|
4
|
+
/** Convert a PCRE regex into JS. */
|
|
5
|
+
function pcre(regex) {
|
|
6
|
+
let finalRegex = regex;
|
|
7
|
+
const replace = (search, replace) => finalRegex = finalRegex.replace(search, replace);
|
|
8
|
+
const finalFlags = new Set();
|
|
9
|
+
// Convert inline flag declarations
|
|
10
|
+
const inlineMatches = regex.matchAll(/\?(-)?([a-z]):/g);
|
|
11
|
+
const startMatches = regex.matchAll(/\(\?(-)?([a-z]+)\)/g);
|
|
12
|
+
for (const [match, isNegative, flags] of [...inlineMatches, ...startMatches]) {
|
|
13
|
+
replace(match, '');
|
|
14
|
+
const func = (flag) => isNegative ? finalFlags.delete(flag) : finalFlags.add(flag);
|
|
15
|
+
[...flags].forEach(func);
|
|
16
|
+
}
|
|
17
|
+
// Remove PCRE-only syntax
|
|
18
|
+
replace(/([*+]){2}/g, '$1');
|
|
19
|
+
replace(/\(\?>/g, '(?:');
|
|
20
|
+
// Remove start/end-of-file markers
|
|
21
|
+
if (/\\[AZ]/.test(finalRegex)) {
|
|
22
|
+
replace(/\\A/g, '^');
|
|
23
|
+
replace(/\\Z/g, '$');
|
|
24
|
+
finalFlags.delete('m');
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
finalFlags.add('m');
|
|
28
|
+
}
|
|
29
|
+
// Reformat free-spacing mode
|
|
30
|
+
if (finalFlags.has('x')) {
|
|
31
|
+
finalFlags.delete('x');
|
|
32
|
+
replace(/#.+/g, '');
|
|
33
|
+
replace(/^\s+|\s+$|\n/gm, '');
|
|
34
|
+
replace(/\s+/g, ' ');
|
|
35
|
+
}
|
|
36
|
+
// Return final regex
|
|
37
|
+
return RegExp(finalRegex, [...finalFlags].join(''));
|
|
38
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
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
|
+
exports.parseGeneratedDataFile = parseGeneratedDataFile;
|
|
7
|
+
exports.default = loadFile;
|
|
8
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const cross_fetch_1 = __importDefault(require("cross-fetch"));
|
|
11
|
+
const node_cache_1 = __importDefault(require("node-cache"));
|
|
12
|
+
const cache = new node_cache_1.default({});
|
|
13
|
+
async function loadWebFile(file) {
|
|
14
|
+
// Return cache if it exists
|
|
15
|
+
const cachedContent = cache.get(file);
|
|
16
|
+
if (cachedContent)
|
|
17
|
+
return cachedContent;
|
|
18
|
+
// Otherwise cache the request
|
|
19
|
+
const dataUrl = (file) => `https://raw.githubusercontent.com/github/linguist/HEAD/lib/linguist/${file}`;
|
|
20
|
+
// Load file content, falling back to the local file if the request fails
|
|
21
|
+
const fileContent = await (0, cross_fetch_1.default)(dataUrl(file)).then(data => data.text()).catch(async () => await loadLocalFile(file));
|
|
22
|
+
cache.set(file, fileContent);
|
|
23
|
+
return fileContent;
|
|
24
|
+
}
|
|
25
|
+
async function loadLocalFile(file) {
|
|
26
|
+
const filePath = node_path_1.default.resolve(__dirname, '../../ext', file);
|
|
27
|
+
return node_fs_1.default.promises.readFile(filePath).then(buffer => buffer.toString());
|
|
28
|
+
}
|
|
29
|
+
/** Nukes unused `generated.rb` file content. */
|
|
30
|
+
async function parseGeneratedDataFile(fileContent) {
|
|
31
|
+
var _a;
|
|
32
|
+
return [...(_a = fileContent.match(/(?<=name\.match\(\/).+?(?=(?<!\\)\/)/gm)) !== null && _a !== void 0 ? _a : []];
|
|
33
|
+
}
|
|
34
|
+
/** Load a data file from github-linguist. */
|
|
35
|
+
async function loadFile(file, offline = false) {
|
|
36
|
+
return offline ? loadLocalFile(file) : loadWebFile(file);
|
|
37
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
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
|
+
exports.normAbsPath = exports.normPath = void 0;
|
|
7
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
8
|
+
const normPath = function normalisedPath(...inputPaths) {
|
|
9
|
+
return node_path_1.default.join(...inputPaths).replace(/\\/g, '/');
|
|
10
|
+
};
|
|
11
|
+
exports.normPath = normPath;
|
|
12
|
+
const normAbsPath = function normalisedAbsolutePath(...inputPaths) {
|
|
13
|
+
return node_path_1.default.resolve(...inputPaths).replace(/\\/g, '/');
|
|
14
|
+
};
|
|
15
|
+
exports.normAbsPath = normAbsPath;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as T from '../types';
|
|
2
|
+
export type FlagAttributes = {
|
|
3
|
+
'vendored': boolean | null;
|
|
4
|
+
'generated': boolean | null;
|
|
5
|
+
'documentation': boolean | null;
|
|
6
|
+
'detectable': boolean | null;
|
|
7
|
+
'binary': boolean | null;
|
|
8
|
+
'language': T.LanguageResult;
|
|
9
|
+
};
|
|
10
|
+
export type ParsedGitattributes = Array<{
|
|
11
|
+
glob: T.FileGlob;
|
|
12
|
+
attrs: FlagAttributes;
|
|
13
|
+
}>;
|
|
14
|
+
/**
|
|
15
|
+
* Parses a gitattributes file.
|
|
16
|
+
*/
|
|
17
|
+
export default function parseAttributes(content: string, folderRoot?: string): ParsedGitattributes;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = parseAttributes;
|
|
4
|
+
const norm_path_1 = require("./norm-path");
|
|
5
|
+
/**
|
|
6
|
+
* Parses a gitattributes file.
|
|
7
|
+
*/
|
|
8
|
+
function parseAttributes(content, folderRoot = '.') {
|
|
9
|
+
var _a, _b;
|
|
10
|
+
const output = [];
|
|
11
|
+
for (const rawLine of content.split('\n')) {
|
|
12
|
+
const line = rawLine.replace(/#.*/, '').trim();
|
|
13
|
+
if (!line)
|
|
14
|
+
continue;
|
|
15
|
+
const parts = line.split(/\s+/g);
|
|
16
|
+
const fileGlob = parts[0];
|
|
17
|
+
const relFileGlob = (0, norm_path_1.normPath)(folderRoot, fileGlob);
|
|
18
|
+
const attrParts = parts.slice(1);
|
|
19
|
+
const isTrue = (str) => !str.startsWith('-') && !str.endsWith('=false');
|
|
20
|
+
const isFalse = (str) => str.startsWith('-') || str.endsWith('=false');
|
|
21
|
+
const trueParts = (str) => attrParts.filter(part => part.includes(str) && isTrue(part));
|
|
22
|
+
const falseParts = (str) => attrParts.filter(part => part.includes(str) && isFalse(part));
|
|
23
|
+
const hasTrueParts = (str) => trueParts(str).length > 0;
|
|
24
|
+
const hasFalseParts = (str) => falseParts(str).length > 0;
|
|
25
|
+
const boolOrNullVal = (str) => hasTrueParts(str) ? true : hasFalseParts(str) ? false : null;
|
|
26
|
+
const attrs = {
|
|
27
|
+
'generated': boolOrNullVal('linguist-generated'),
|
|
28
|
+
'vendored': boolOrNullVal('linguist-vendored'),
|
|
29
|
+
'documentation': boolOrNullVal('linguist-documentation'),
|
|
30
|
+
'detectable': boolOrNullVal('linguist-detectable'),
|
|
31
|
+
'binary': hasTrueParts('binary') || hasFalseParts('text') ? true : hasFalseParts('binary') || hasTrueParts('text') ? false : null,
|
|
32
|
+
'language': (_b = (_a = trueParts('linguist-language').at(-1)) === null || _a === void 0 ? void 0 : _a.split('=')[1]) !== null && _b !== void 0 ? _b : null,
|
|
33
|
+
};
|
|
34
|
+
output.push({ glob: relFileGlob, attrs });
|
|
35
|
+
}
|
|
36
|
+
return output;
|
|
37
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function parseGitignore(content: string): string[];
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = parseGitignore;
|
|
4
|
+
function parseGitignore(content) {
|
|
5
|
+
const readableData = content
|
|
6
|
+
// Remove comments unless escaped
|
|
7
|
+
.replace(/(?<!\\)#.+/g, '')
|
|
8
|
+
// Remove whitespace unless escaped
|
|
9
|
+
.replace(/(?:(?<!\\)\s)+$/g, '');
|
|
10
|
+
const arrayData = readableData.split(/\r?\n/).filter(data => data);
|
|
11
|
+
return arrayData;
|
|
12
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
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
|
+
exports.default = readFileChunk;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
/**
|
|
9
|
+
* Read part of a file on disc.
|
|
10
|
+
* @throws 'EPERM' if the file is not readable.
|
|
11
|
+
*/
|
|
12
|
+
async function readFileChunk(filename, onlyFirstLine = false) {
|
|
13
|
+
const chunkSize = 100;
|
|
14
|
+
const stream = node_fs_1.default.createReadStream(filename, { highWaterMark: chunkSize });
|
|
15
|
+
let content = '';
|
|
16
|
+
for await (const data of stream) { // may throw
|
|
17
|
+
content += data.toString();
|
|
18
|
+
if (onlyFirstLine && content.includes('\n')) {
|
|
19
|
+
return content.split(/\r?\n/)[0];
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return content;
|
|
23
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Ignore } from 'ignore';
|
|
2
|
+
interface WalkInput {
|
|
3
|
+
/** Whether this is walking the tree from the root */
|
|
4
|
+
init: boolean;
|
|
5
|
+
/** The common root absolute path of all folders being checked */
|
|
6
|
+
commonRoot: string;
|
|
7
|
+
/** The absolute path that each folder is relative to */
|
|
8
|
+
folderRoots: string[];
|
|
9
|
+
/** The absolute path of folders being checked */
|
|
10
|
+
folders: string[];
|
|
11
|
+
/** An instantiated Ignore object listing ignored files */
|
|
12
|
+
ignored: Ignore;
|
|
13
|
+
}
|
|
14
|
+
interface WalkOutput {
|
|
15
|
+
files: string[];
|
|
16
|
+
folders: string[];
|
|
17
|
+
}
|
|
18
|
+
/** Generate list of files in a directory. */
|
|
19
|
+
export default function walk(data: WalkInput): WalkOutput;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,85 @@
|
|
|
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
|
+
exports.default = walk;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const parse_gitignore_1 = __importDefault(require("./parse-gitignore"));
|
|
10
|
+
const norm_path_1 = require("./norm-path");
|
|
11
|
+
let allFiles;
|
|
12
|
+
let allFolders;
|
|
13
|
+
;
|
|
14
|
+
;
|
|
15
|
+
/** Generate list of files in a directory. */
|
|
16
|
+
function walk(data) {
|
|
17
|
+
const { init, commonRoot, folderRoots, folders, ignored } = data;
|
|
18
|
+
// Initialise files and folders lists
|
|
19
|
+
if (init) {
|
|
20
|
+
allFiles = new Set();
|
|
21
|
+
allFolders = new Set();
|
|
22
|
+
}
|
|
23
|
+
// Walk tree of a folder
|
|
24
|
+
if (folders.length === 1) {
|
|
25
|
+
const folder = folders[0];
|
|
26
|
+
const localRoot = folderRoots[0].replace(commonRoot, '').replace(/^\//, '');
|
|
27
|
+
// Get list of files and folders inside this folder
|
|
28
|
+
const files = node_fs_1.default.readdirSync(folder).map(file => {
|
|
29
|
+
// Create path relative to root
|
|
30
|
+
const base = (0, norm_path_1.normAbsPath)(folder, file).replace(commonRoot, '.');
|
|
31
|
+
// Add trailing slash to mark directories
|
|
32
|
+
const isDir = node_fs_1.default.lstatSync(node_path_1.default.resolve(commonRoot, base)).isDirectory();
|
|
33
|
+
return isDir ? `${base}/` : base;
|
|
34
|
+
});
|
|
35
|
+
// Read and apply gitignores
|
|
36
|
+
const gitignoreFilename = (0, norm_path_1.normPath)(folder, '.gitignore');
|
|
37
|
+
if (node_fs_1.default.existsSync(gitignoreFilename)) {
|
|
38
|
+
const gitignoreContents = node_fs_1.default.readFileSync(gitignoreFilename, 'utf-8');
|
|
39
|
+
const ignoredPaths = (0, parse_gitignore_1.default)(gitignoreContents);
|
|
40
|
+
ignored.add(ignoredPaths);
|
|
41
|
+
}
|
|
42
|
+
// Add gitattributes if present
|
|
43
|
+
const gitattributesPath = (0, norm_path_1.normPath)(folder, '.gitattributes');
|
|
44
|
+
if (node_fs_1.default.existsSync(gitattributesPath)) {
|
|
45
|
+
allFiles.add(gitattributesPath);
|
|
46
|
+
}
|
|
47
|
+
// Loop through files and folders
|
|
48
|
+
for (const file of files) {
|
|
49
|
+
// Create absolute path for disc operations
|
|
50
|
+
const path = (0, norm_path_1.normAbsPath)(commonRoot, file);
|
|
51
|
+
const localPath = localRoot ? file.replace(`./${localRoot}/`, '') : file.replace('./', '');
|
|
52
|
+
// Skip if nonexistant
|
|
53
|
+
const nonExistant = !node_fs_1.default.existsSync(path);
|
|
54
|
+
if (nonExistant)
|
|
55
|
+
continue;
|
|
56
|
+
// Skip if marked in gitignore
|
|
57
|
+
const isIgnored = ignored.test(localPath).ignored;
|
|
58
|
+
if (isIgnored)
|
|
59
|
+
continue;
|
|
60
|
+
// Add absolute folder path to list
|
|
61
|
+
allFolders.add((0, norm_path_1.normAbsPath)(folder));
|
|
62
|
+
// Check if this is a folder or file
|
|
63
|
+
if (file.endsWith('/')) {
|
|
64
|
+
// Recurse into subfolders
|
|
65
|
+
allFolders.add(path);
|
|
66
|
+
walk({ init: false, commonRoot, folderRoots, folders: [path], ignored });
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
// Add file path to list
|
|
70
|
+
allFiles.add(path);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// Recurse into all folders
|
|
75
|
+
else {
|
|
76
|
+
for (const i in folders) {
|
|
77
|
+
walk({ init: false, commonRoot, folderRoots: [folderRoots[i]], folders: [folders[i]], ignored });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// Return absolute files and folders lists
|
|
81
|
+
return {
|
|
82
|
+
files: [...allFiles].map(file => file.replace(/^\./, commonRoot)),
|
|
83
|
+
folders: [...allFolders],
|
|
84
|
+
};
|
|
85
|
+
}
|