linguist-js 3.0.0-pre → 3.0.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.
Files changed (59) hide show
  1. package/dist/analyser/pipeline/aggregate.d.ts +3 -1
  2. package/dist/analyser/pipeline/aggregate.js +8 -8
  3. package/dist/analyser/pipeline/classify.js +2 -1
  4. package/dist/program/data/loadDataFiles.js +9 -1
  5. package/dist/program/data/retrieveData.d.ts +1 -4
  6. package/dist/program/data/retrieveData.js +3 -6
  7. package/dist/program/utils/pcre.js +1 -0
  8. package/dist/src/cli/output/default.d.ts +3 -0
  9. package/dist/src/cli/output/default.js +106 -0
  10. package/dist/src/cli/output/tree.d.ts +3 -0
  11. package/dist/src/cli/output/tree.js +11 -0
  12. package/dist/src/cli/runCliAnalysis.d.ts +2 -0
  13. package/dist/src/cli/runCliAnalysis.js +25 -0
  14. package/dist/src/cli/utils.d.ts +2 -0
  15. package/dist/src/cli/utils.js +9 -0
  16. package/dist/src/cli.d.ts +1 -0
  17. package/dist/src/cli.js +56 -0
  18. package/dist/src/helpers/convert-pcre.d.ts +2 -0
  19. package/dist/src/helpers/convert-pcre.js +38 -0
  20. package/dist/src/helpers/load-data.d.ts +4 -0
  21. package/dist/src/helpers/load-data.js +37 -0
  22. package/dist/src/helpers/norm-path.d.ts +2 -0
  23. package/dist/src/helpers/norm-path.js +15 -0
  24. package/dist/src/helpers/parse-gitattributes.d.ts +17 -0
  25. package/dist/src/helpers/parse-gitattributes.js +37 -0
  26. package/dist/src/helpers/parse-gitignore.d.ts +1 -0
  27. package/dist/src/helpers/parse-gitignore.js +12 -0
  28. package/dist/src/helpers/read-file.d.ts +5 -0
  29. package/dist/src/helpers/read-file.js +23 -0
  30. package/dist/src/helpers/walk-tree.d.ts +20 -0
  31. package/dist/src/helpers/walk-tree.js +90 -0
  32. package/dist/src/index.d.ts +5 -0
  33. package/dist/src/index.js +470 -0
  34. package/dist/src/program/data/loadData.d.ts +4 -0
  35. package/dist/src/program/data/loadData.js +32 -0
  36. package/dist/src/program/fs/normalisedPath.d.ts +2 -0
  37. package/dist/src/program/fs/normalisedPath.js +7 -0
  38. package/dist/src/program/fs/readFile.d.ts +5 -0
  39. package/dist/src/program/fs/readFile.js +17 -0
  40. package/dist/src/program/fs/walkTree.d.ts +20 -0
  41. package/dist/src/program/fs/walkTree.js +82 -0
  42. package/dist/src/program/parsing/parseGitattributes.d.ts +17 -0
  43. package/dist/src/program/parsing/parseGitattributes.js +33 -0
  44. package/dist/src/program/parsing/parseGitignore.d.ts +1 -0
  45. package/dist/src/program/parsing/parseGitignore.js +9 -0
  46. package/dist/src/program/utils/pcre.d.ts +2 -0
  47. package/dist/src/program/utils/pcre.js +35 -0
  48. package/dist/src/schema.d.ts +37 -0
  49. package/dist/src/schema.js +2 -0
  50. package/dist/src/types/schema.d.ts +37 -0
  51. package/dist/src/types/schema.js +1 -0
  52. package/dist/src/types/types.d.ts +65 -0
  53. package/dist/src/types/types.js +1 -0
  54. package/dist/src/types.d.ts +74 -0
  55. package/dist/src/types.js +2 -0
  56. package/dist/types/types.d.ts +0 -1
  57. package/ext/languages.yml +8 -0
  58. package/package.json +2 -2
  59. package/readme.md +20 -37
@@ -1,2 +1,4 @@
1
1
  import * as T from '../../types/types.js';
2
- export declare function aggregateResults(files: T.VirtualFile[], classifications: Record<string, string[]>, heuristicResolutions: Record<string, string | undefined>, langData: Record<string, unknown>, opts: T.Options): T.Results;
2
+ export declare function aggregateResults(files: T.VirtualFile[], classifications: Record<string, string[]>, heuristicResolutions: Record<string, string | undefined>, langData: {
3
+ [language: string]: T.LanguageMetadata;
4
+ }, opts: T.Options): T.Results;
@@ -1,6 +1,7 @@
1
1
  import Path from 'node:path';
2
2
  const categoryKeys = ['data', 'markup', 'programming', 'prose'];
3
3
  function pickBestLanguage(classifications) {
4
+ // Assign first language as a default option
4
5
  return classifications[0] ?? null;
5
6
  }
6
7
  function makeRelPath(path) {
@@ -15,7 +16,7 @@ function normPath(filePath) {
15
16
  }
16
17
  export function aggregateResults(files, classifications, heuristicResolutions, langData, opts) {
17
18
  const results = {
18
- files: { count: 0, bytes: 0, lines: { total: 0, content: 0 }, results: {}, alternatives: {} },
19
+ files: { count: 0, bytes: 0, lines: { total: 0, content: 0 }, results: {} },
19
20
  languages: { count: 0, bytes: 0, lines: { total: 0, content: 0 }, results: {} },
20
21
  unknown: { count: 0, bytes: 0, lines: { total: 0, content: 0 }, extensions: {}, filenames: {} },
21
22
  repository: {},
@@ -27,10 +28,12 @@ export function aggregateResults(files, classifications, heuristicResolutions, l
27
28
  // Narrow down file associations to the best fit
28
29
  const candidates = classifications[file.path] ?? [];
29
30
  // If no heuristics, assign a language
30
- const selectedLanguage = heuristicResolutions[file.path] ?? pickBestLanguage(candidates);
31
- const alternativeLanguages = [...new Set(candidates.filter((lang) => lang !== selectedLanguage))];
32
- // Assign first language as a default option
33
- // List alternative languages if there are any
31
+ const bestLanguage = heuristicResolutions[file.path] ?? pickBestLanguage(candidates);
32
+ const selectedLanguage = bestLanguage
33
+ ? opts.childLanguages
34
+ ? bestLanguage // use the child language
35
+ : (langData[bestLanguage]?.group ?? bestLanguage) // use the parent language, if it exists
36
+ : null;
34
37
  // Load language bytes size
35
38
  const size = file.size ?? file.content?.length ?? 0;
36
39
  // Calculate lines of code
@@ -72,9 +75,6 @@ export function aggregateResults(files, classifications, heuristicResolutions, l
72
75
  }
73
76
  }
74
77
  results.files.results[outputPath] = selectedLanguage;
75
- if (alternativeLanguages.length) {
76
- results.files.alternatives[outputPath] = alternativeLanguages;
77
- }
78
78
  // Apply to files totals
79
79
  results.files.bytes += size;
80
80
  results.files.lines.total += Number.isNaN(loc.total) ? 0 : loc.total;
@@ -18,11 +18,12 @@ export function classifyFiles(files, langData, opts) {
18
18
  const classifications = {};
19
19
  for (const file of files) {
20
20
  // Search each language
21
+ // Note: order here is important; the first match will be the one chosen
21
22
  const candidates = [
22
23
  ...byAttributes(file, langData),
24
+ ...byModeline(file, langData, opts),
23
25
  ...byFilename(file, langData),
24
26
  ...byShebang(file, langData, opts),
25
- ...byModeline(file, langData, opts),
26
27
  ...byExtension(file, langData),
27
28
  ];
28
29
  classifications[file.path] = dedupeClassifications(candidates);
@@ -1,3 +1,4 @@
1
+ import YAML from 'js-yaml';
1
2
  import Cache from 'node-cache';
2
3
  import FS from 'node:fs';
3
4
  import Path from 'node:path';
@@ -14,8 +15,15 @@ async function loadWebFile(file) {
14
15
  // Load file content, falling back to the local file if the request fails
15
16
  const fileContent = await fetch(dataUrl(file))
16
17
  .then((data) => data.text())
17
- .catch(async () => await loadLocalFile(file));
18
+ .catch(async (x) => void x);
19
+ if (!fileContent) {
20
+ return await loadLocalFile(file);
21
+ }
18
22
  cache.set(file, fileContent);
23
+ // Clean up lengthy files
24
+ if (file === 'generated.rb') {
25
+ return YAML.dump(parseGeneratedDataFile(fileContent));
26
+ }
19
27
  return fileContent;
20
28
  }
21
29
  async function loadLocalFile(file) {
@@ -1,10 +1,7 @@
1
- import { HeuristicsSchema, LanguagesScema, VendorSchema } from '../../types/schema.js';
1
+ import { HeuristicsSchema, LanguagesScema } from '../../types/schema.js';
2
2
  type LoadedData = {
3
3
  langData: LanguagesScema;
4
- vendorData: VendorSchema;
5
- docData: VendorSchema;
6
4
  heuristicsData: HeuristicsSchema;
7
- generatedData: string[];
8
5
  vendorPaths: string[];
9
6
  };
10
7
  /** Load data from github-linguist web repo or cached local file. */
@@ -1,22 +1,19 @@
1
1
  import YAML from 'js-yaml';
2
- import { loadFile, parseGeneratedDataFile } from './loadDataFiles.js';
2
+ import { loadFile } from './loadDataFiles.js';
3
3
  let data = null;
4
4
  async function initRetrieveData(offline) {
5
- // Only load the data on mont
5
+ // Only load the data on mount
6
6
  if (data)
7
7
  return;
8
8
  const langData = (await loadFile('languages.yml', offline).then(YAML.load));
9
9
  const vendorData = (await loadFile('vendor.yml', offline).then(YAML.load));
10
10
  const docData = (await loadFile('documentation.yml', offline).then(YAML.load));
11
11
  const heuristicsData = (await loadFile('heuristics.yml', offline).then(YAML.load));
12
- const generatedData = (await loadFile('generated.rb', offline).then(parseGeneratedDataFile));
12
+ const generatedData = (await loadFile('generated.rb', offline).then(YAML.load));
13
13
  const vendorPaths = [...vendorData, ...docData, ...generatedData];
14
14
  data = {
15
15
  langData,
16
- vendorData,
17
- docData,
18
16
  heuristicsData,
19
- generatedData,
20
17
  vendorPaths,
21
18
  };
22
19
  }
@@ -14,6 +14,7 @@ export default function pcre(regex) {
14
14
  // Remove PCRE-only syntax
15
15
  replace(/([*+]){2}/g, '$1');
16
16
  replace(/\(\?>/g, '(?:');
17
+ replace(/\\g\<[^>]+\>/g, '.*?'); // referenced named groups not supported
17
18
  // Remove start/end-of-file markers
18
19
  if (/\\[AZ]/.test(finalRegex)) {
19
20
  replace(/\\A/g, '^');
@@ -0,0 +1,3 @@
1
+ import { OptionValues } from 'commander';
2
+ import { Results } from '../../types/types.js';
3
+ export default function defaultOutput(args: OptionValues, data: Results): Promise<void>;
@@ -0,0 +1,106 @@
1
+ import FS from 'node:fs';
2
+ import Path from 'node:path';
3
+ import { normPath } from '../../program/fs/normalisedPath.js';
4
+ import { colouredMsg, hexToRgb } from '../utils.js';
5
+ export default async function defaultOutput(args, data) {
6
+ const { files, languages, unknown, repository } = data;
7
+ if (args.minSize) {
8
+ // Ignore languages with a bytes/% size less than the declared min size
9
+ const totalSize = languages.bytes;
10
+ const minSizeAmt = parseFloat(args.minSize.replace(/[a-z]+$/i, '')); // '2KB' -> 2
11
+ const minSizeUnit = args.minSize.replace(/^\d+/, '').toLowerCase(); // '2KB' -> 'kb'
12
+ const checkBytes = minSizeUnit !== 'loc'; // whether to check bytes or loc
13
+ const conversionFactors = {
14
+ ['b']: (n) => n,
15
+ ['kb']: (n) => n * 1e3,
16
+ ['mb']: (n) => n * 1e6,
17
+ ['%']: (n) => (n * totalSize) / 100,
18
+ ['loc']: (n) => n,
19
+ };
20
+ const minBytesSize = conversionFactors[minSizeUnit](+minSizeAmt);
21
+ const other = { count: 0, bytes: 0, lines: { total: 0, content: 0, code: 0 } };
22
+ // Apply specified minimums: delete language results that do not reach the threshold
23
+ for (const [lang, data] of Object.entries(languages.results)) {
24
+ const checkUnit = checkBytes ? data.bytes : data.lines.content;
25
+ if (checkUnit < minBytesSize) {
26
+ // Add to 'other' count
27
+ other.count++;
28
+ other.bytes += data.bytes;
29
+ other.lines.total += data.lines.total;
30
+ other.lines.content += data.lines.content;
31
+ // Remove language result
32
+ delete languages.results[lang];
33
+ }
34
+ }
35
+ if (other.bytes) {
36
+ languages.results['Other'] = other;
37
+ }
38
+ }
39
+ const sortedEntries = Object.entries(languages.results).sort((a, b) => (a[1].bytes < b[1].bytes ? +1 : -1));
40
+ const totalBytes = languages.bytes;
41
+ console.log(`\n Analysed ${files.bytes.toLocaleString()} B from ${files.count} files with linguist-js`);
42
+ console.log(`\n Language analysis results: \n`);
43
+ let count = 0;
44
+ if (sortedEntries.length === 0)
45
+ console.log(` None`);
46
+ // Collate files per language
47
+ const filesPerLanguage = {};
48
+ if (args.listFiles) {
49
+ for (const language of Object.keys(languages.results)) {
50
+ filesPerLanguage[language] = [];
51
+ }
52
+ for (const [file, lang] of Object.entries(files.results)) {
53
+ if (lang) {
54
+ filesPerLanguage[lang].push(file);
55
+ }
56
+ }
57
+ }
58
+ // List parsed results
59
+ for (const [lang, { bytes, lines }] of sortedEntries) {
60
+ const colour = hexToRgb(repository[lang].color ?? '#ededed');
61
+ const percent = (bytes) => (bytes / (totalBytes || 1)) * 100;
62
+ const fmtd = {
63
+ index: (++count).toString().padStart(2, ' '),
64
+ lang: lang.padEnd(24, ' '),
65
+ percent: percent(bytes).toFixed(2).padStart(5, ' '),
66
+ bytes: bytes.toLocaleString().padStart(10, ' '),
67
+ loc: lines.content.toLocaleString().padStart(10, ' '),
68
+ icon: colouredMsg(colour, '\u2588'),
69
+ };
70
+ console.log(` ${fmtd.index}. ${fmtd.icon} ${fmtd.lang} ${fmtd.percent}% ${fmtd.bytes} B ${fmtd.loc} LOC`);
71
+ // If using `listFiles` option, list all files tagged as this language
72
+ if (args.listFiles) {
73
+ console.log(); // padding
74
+ for (const file of filesPerLanguage[lang]) {
75
+ let relFile = normPath(Path.relative(Path.resolve('.'), file));
76
+ if (!relFile.startsWith('../')) {
77
+ relFile = './' + relFile;
78
+ }
79
+ const fileStat = await FS.promises.stat(file);
80
+ const bytes = fileStat.size;
81
+ const fmtd2 = {
82
+ file: relFile.padEnd(42, ' '),
83
+ percent: percent(bytes).toFixed(2).padStart(5, ' '),
84
+ bytes: bytes.toLocaleString().padStart(10, ' '),
85
+ };
86
+ console.log(` ${fmtd.icon} ${fmtd2.file} ${fmtd2.percent}% ${fmtd2.bytes} B`);
87
+ }
88
+ console.log(); // padding
89
+ }
90
+ }
91
+ if (!args.listFiles) {
92
+ console.log(); // padding
93
+ }
94
+ console.log(` Total: ${totalBytes.toLocaleString()} B`);
95
+ // List unknown files/extensions
96
+ if (unknown.bytes > 0) {
97
+ console.log(`\n Unknown files and extensions:`);
98
+ for (const [name, bytes] of Object.entries(unknown.filenames)) {
99
+ console.log(` '${name}': ${bytes.toLocaleString()} B`);
100
+ }
101
+ for (const [ext, bytes] of Object.entries(unknown.extensions)) {
102
+ console.log(` '*${ext}': ${bytes.toLocaleString()} B`);
103
+ }
104
+ console.log(` Total: ${unknown.bytes.toLocaleString()} B`);
105
+ }
106
+ }
@@ -0,0 +1,3 @@
1
+ import { OptionValues } from 'commander';
2
+ import { Results } from '../../types/types.js';
3
+ export default function treeOutput(args: OptionValues, data: Results): void;
@@ -0,0 +1,11 @@
1
+ export default function treeOutput(args, data) {
2
+ const treeParts = args.tree.split('.');
3
+ let nestedData = data;
4
+ for (const part of treeParts) {
5
+ if (!nestedData[part]) {
6
+ throw Error(`TraversalError: Key '${part}' cannot be found on output object.`);
7
+ }
8
+ nestedData = nestedData[part];
9
+ }
10
+ console.log(nestedData);
11
+ }
@@ -0,0 +1,2 @@
1
+ import { OptionValues } from 'commander';
2
+ export default function runCliAnalysis(args: OptionValues): Promise<void>;
@@ -0,0 +1,25 @@
1
+ import linguist from '../index.js';
2
+ import defaultOutput from './output/default.js';
3
+ import treeOutput from './output/tree.js';
4
+ const validCategories = ['data', 'programming', 'prose', 'markup'];
5
+ export default async function runCliAnalysis(args) {
6
+ // Check arguments
7
+ if (args.categories?.some((category) => !validCategories.includes(category))) {
8
+ console.log(`Error: '${args.categories.join(', ')}' contains an invalid category.`);
9
+ console.log(`\tValid options: ${validCategories.join(', ')}.`);
10
+ return;
11
+ }
12
+ // Fetch language data
13
+ const root = args.analyze === true ? '.' : args.analyze;
14
+ const data = await linguist(root, args);
15
+ // Print output
16
+ if (!args.json) {
17
+ defaultOutput(args, data);
18
+ }
19
+ else if (args.tree) {
20
+ treeOutput(args, data);
21
+ }
22
+ else {
23
+ console.dir(data, { depth: null });
24
+ }
25
+ }
@@ -0,0 +1,2 @@
1
+ export declare function colouredMsg([r, g, b]: [number, number, number], msg: string): string;
2
+ export declare function hexToRgb(hex: string): [number, number, number];
@@ -0,0 +1,9 @@
1
+ export function colouredMsg([r, g, b], msg) {
2
+ return `\u001B[${38};2;${r};${g};${b}m${msg}\u001b[0m`;
3
+ }
4
+ export function hexToRgb(hex) {
5
+ const r = parseInt(hex.slice(1, 3), 16);
6
+ const g = parseInt(hex.slice(3, 5), 16);
7
+ const b = parseInt(hex.slice(5, 7), 16);
8
+ return [r, g, b];
9
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,56 @@
1
+ import { program } from 'commander';
2
+ import FS from 'node:fs';
3
+ import runCliAnalysis from './cli/runCliAnalysis.js';
4
+ const packageJson = JSON.parse(FS.readFileSync(new URL('../package.json', import.meta.url), 'utf-8'));
5
+ const VERSION = packageJson.version;
6
+ program
7
+ .name('linguist')
8
+ .usage('--analyze [<folders...>] [<options...>]')
9
+ .option('-a|--analyze [folders...]', 'Analyse the languages of all files in a folder')
10
+ .option('-i|--ignoredFiles <files...>', `A list of file path globs to ignore`)
11
+ .option('-l|--ignoredLanguages <languages...>', `A list of languages to ignore`)
12
+ .option('-c|--categories <categories...>', 'Language categories to include in output')
13
+ .option('-C|--childLanguages [bool]', 'Display child languages instead of their parents', false)
14
+ .option('-j|--json [bool]', 'Display the output as JSON', false)
15
+ .option('-t|--tree <traversal>', 'Which part of the output JSON to display (dot-delimited)')
16
+ .option('-F|--listFiles [bool]', 'Whether to list every matching file under the language results', false)
17
+ .option('-m|--minSize <size>', 'Minimum size of file to show language results for (must have a unit: b, kb, mb, %, or loc)')
18
+ .option('-q|--quick [bool]', 'Skip complex language analysis (alias for -{A|I|H|S}=false)', false)
19
+ .option('-o|--offline [bool]', 'Use packaged data files instead of fetching latest from GitHub', false)
20
+ .option('-L|--calculateLines [bool]', 'Calculate lines of code totals', true)
21
+ .option('-V|--keepVendored [bool]', 'Prevent skipping over vendored/generated files', false)
22
+ .option('-B|--keepBinary [bool]', 'Prevent skipping over binary files', false)
23
+ .option('-r|--relativePaths [bool]', 'Convert absolute file paths to relative', false)
24
+ .option('-A|--checkAttributes [bool]', 'Force the checking of gitattributes files', true)
25
+ .option('-I|--checkIgnored [bool]', 'Force the checking of gitignore files', true)
26
+ .option('-D|--checkDetected [bool]', 'Force files marked with linguist-detectable to always appear in output', true)
27
+ .option('-H|--checkHeuristics [bool]', 'Apply heuristics to ambiguous languages', true)
28
+ .option('-S|--checkShebang [bool]', 'Check shebang lines for explicit classification', true)
29
+ .option('-M|--checkModeline [bool]', 'Check modelines for explicit classification', true)
30
+ .helpOption(`-h|--help`, 'Display this help message')
31
+ .version(VERSION, '-v|--version', 'Display the installed version of linguist-js');
32
+ program.parse(process.argv);
33
+ const args = program.opts();
34
+ // Normalise arguments
35
+ for (const arg in args) {
36
+ const normalise = (val) => {
37
+ if (typeof val !== 'string')
38
+ return val;
39
+ val = val.replace(/^=/, '');
40
+ if (val.match(/true$|false$/))
41
+ val = val === 'true';
42
+ return val;
43
+ };
44
+ if (Array.isArray(args[arg]))
45
+ args[arg] = args[arg].map(normalise);
46
+ else
47
+ args[arg] = normalise(args[arg]);
48
+ }
49
+ // Run Linguist
50
+ if (args.analyze) {
51
+ void runCliAnalysis(args);
52
+ }
53
+ else {
54
+ console.log(`Welcome to linguist-js, a JavaScript port of GitHub's language analyzer.`);
55
+ console.log(`Type 'linguist --help' for a list of commands.`);
56
+ }
@@ -0,0 +1,2 @@
1
+ /** Convert a PCRE regex into JS. */
2
+ export default function pcre(regex: string): RegExp;
@@ -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,4 @@
1
+ /** Nukes unused `generated.rb` file content. */
2
+ export declare function parseGeneratedDataFile(fileContent: string): string[];
3
+ /** Load a data file from github-linguist. */
4
+ export default function loadFile(file: string, offline?: boolean): Promise<string>;
@@ -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 fs_1 = __importDefault(require("fs"));
9
+ const path_1 = __importDefault(require("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 = path_1.default.resolve(__dirname, '../../ext', file);
27
+ return fs_1.default.promises.readFile(filePath).then(buffer => buffer.toString());
28
+ }
29
+ /** Nukes unused `generated.rb` file content. */
30
+ 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
+ function loadFile(file, offline = false) {
36
+ return offline ? loadLocalFile(file) : loadWebFile(file);
37
+ }
@@ -0,0 +1,2 @@
1
+ export declare const normPath: (...inputPaths: string[]) => string;
2
+ export declare const normAbsPath: (...inputPaths: string[]) => string;
@@ -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 path_1 = __importDefault(require("path"));
8
+ const normPath = function normalisedPath(...inputPaths) {
9
+ return path_1.default.join(...inputPaths).replace(/\\/g, '/');
10
+ };
11
+ exports.normPath = normPath;
12
+ const normAbsPath = function normalisedAbsolutePath(...inputPaths) {
13
+ return 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,5 @@
1
+ /**
2
+ * Read part of a file on disc.
3
+ * @throws 'EPERM' if the file is not readable.
4
+ */
5
+ export default function readFileChunk(filename: string, onlyFirstLine?: boolean): Promise<string>;
@@ -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 fs_1 = __importDefault(require("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 = 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 {};