parse-gitignore
Advanced tools
+219
-25
| /*! | ||
| * parse-gitignore <https://github.com/jonschlinkert/parse-gitignore> | ||
| * | ||
| * Copyright (c) 2015-present, Jon Schlinkert. | ||
@@ -10,43 +9,238 @@ * Released under the MIT License. | ||
| const gitignore = input => { | ||
| return input.toString() | ||
| .split(/\r?\n/) | ||
| .filter(l => l.trim() !== '' && l.charAt(0) !== '#'); | ||
| const fs = require('fs'); | ||
| const isObject = v => v !== null && typeof v === 'object' && !Array.isArray(v); | ||
| // eslint-disable-next-line no-control-regex | ||
| const INVALID_PATH_CHARS_REGEX = /[<>:"|?*\n\r\t\f\x00-\x1F]/; | ||
| const GLOBSTAR_REGEX = /(?:^|\/)[*]{2}($|\/)/; | ||
| const MAX_PATH_LENGTH = 260 - 12; | ||
| const isValidPath = input => { | ||
| if (typeof input === 'string') { | ||
| return input.length <= MAX_PATH_LENGTH && !INVALID_PATH_CHARS_REGEX.test(input); | ||
| } | ||
| return false; | ||
| }; | ||
| gitignore.parse = (input, fn = line => line) => { | ||
| let lines = input.toString().split(/\r?\n/); | ||
| let state = { patterns: [], sections: [] }; | ||
| const split = str => String(str).split(/\r\n?|\n/); | ||
| const isComment = str => str.startsWith('#'); | ||
| const isParsed = input => isObject(input) && input.patterns && input.sections; | ||
| const patterns = input => { | ||
| return split(input).map(l => l.trim()).filter(line => line !== '' && !isComment(line)); | ||
| }; | ||
| const parse = (input, options = {}) => { | ||
| let filepath = options.path; | ||
| if (isParsed(input)) return input; | ||
| if (isValidPath(input) && fs.existsSync(input)) { | ||
| filepath = input; | ||
| input = fs.readFileSync(input); | ||
| } | ||
| const lines = split(input); | ||
| const names = new Map(); | ||
| let parsed = { sections: [], patterns: [] }; | ||
| let section = { name: 'default', patterns: [] }; | ||
| let prev; | ||
| for (let line of lines) { | ||
| if (line.charAt(0) === '#') { | ||
| section = { name: line.slice(1).trim(), patterns: []}; | ||
| state.sections.push(section); | ||
| for (const line of lines) { | ||
| const value = line.trim(); | ||
| if (value.startsWith('#')) { | ||
| const [, name] = /^#+\s*(.*)\s*$/.exec(value); | ||
| if (prev) { | ||
| names.delete(prev.name); | ||
| prev.comment += value ? `\n${value}` : ''; | ||
| prev.name = name ? `${prev.name.trim()}\n${name.trim()}` : prev.name.trim(); | ||
| names.set(prev.name.toLowerCase().trim(), prev); | ||
| continue; | ||
| } | ||
| section = { name: name.trim(), comment: value, patterns: [] }; | ||
| names.set(section.name.toLowerCase(), section); | ||
| parsed.sections.push(section); | ||
| prev = section; | ||
| continue; | ||
| } | ||
| if (line.trim() !== '') { | ||
| let pattern = fn(line, section, state); | ||
| section.patterns.push(pattern); | ||
| state.patterns.push(pattern); | ||
| if (value !== '') { | ||
| section.patterns.push(value); | ||
| parsed.patterns.push(value); | ||
| } | ||
| prev = null; | ||
| } | ||
| return state; | ||
| if (options.dedupe === true || options.unique === true) { | ||
| parsed = dedupe(parsed, { ...options, format: false }); | ||
| } | ||
| parsed.path = filepath; | ||
| parsed.input = Buffer.from(input); | ||
| parsed.format = opts => format(parsed, { ...options, ...opts }); | ||
| parsed.dedupe = opts => dedupe(parsed, { ...options, ...opts }); | ||
| parsed.globs = opts => globs(parsed, { path: filepath, ...options, ...opts }); | ||
| return parsed; | ||
| }; | ||
| gitignore.format = (section) => { | ||
| return `# ${section.name}\n${section.patterns.join('\n')}\n\n`; | ||
| const parseFile = (filepath, options) => { | ||
| return parse(fs.readFileSync(filepath, 'utf8'), options); | ||
| }; | ||
| gitignore.stringify = (sections, fn = gitignore.format) => { | ||
| let result = ''; | ||
| for (let section of [].concat(sections)) result += fn(section); | ||
| return result.trim(); | ||
| const dedupe = (input, options) => { | ||
| const parsed = parse(input, { ...options, dedupe: false }); | ||
| const names = new Map(); | ||
| const res = { sections: [], patterns: new Set() }; | ||
| let current; | ||
| // first, combine duplicate sections | ||
| for (const section of parsed.sections) { | ||
| const { name = '', comment, patterns } = section; | ||
| const key = name.trim().toLowerCase(); | ||
| for (const pattern of patterns) { | ||
| res.patterns.add(pattern); | ||
| } | ||
| if (name && names.has(key)) { | ||
| current = names.get(key); | ||
| current.patterns = [...current.patterns, ...patterns]; | ||
| } else { | ||
| current = { name, comment, patterns }; | ||
| res.sections.push(current); | ||
| names.set(key, current); | ||
| } | ||
| } | ||
| // next, de-dupe patterns in each section | ||
| for (const section of res.sections) { | ||
| section.patterns = [...new Set(section.patterns)]; | ||
| } | ||
| res.patterns = [...res.patterns]; | ||
| return res; | ||
| }; | ||
| const glob = (pattern, options) => { | ||
| // Return if a glob pattern has already been specified for sub-directories | ||
| if (GLOBSTAR_REGEX.test(pattern)) { | ||
| return pattern; | ||
| } | ||
| // If there is a separator at the beginning or middle (or both) of the pattern, | ||
| // then the pattern is relative to the directory level of the particular .gitignore | ||
| // file itself. Otherwise the pattern may also match at any level below the | ||
| // .gitignore level. relative paths only | ||
| let relative = false; | ||
| if (pattern.startsWith('/')) { | ||
| pattern = pattern.slice(1); | ||
| relative = true; | ||
| } else if (pattern.slice(1, pattern.length - 1).includes('/')) { | ||
| relative = true; | ||
| } | ||
| // If there is a separator at the end of the pattern then the pattern will only match directories. | ||
| pattern += pattern.endsWith('/') ? '**/' : '/**'; | ||
| // If not relative, the pattern can match any files and directories. | ||
| return relative ? pattern : `**/${pattern}`; | ||
| }; | ||
| const globs = (input, options = {}) => { | ||
| const parsed = parse(input, options); | ||
| const result = []; | ||
| let index = 0; | ||
| const patterns = parsed.patterns | ||
| .concat(options.ignore || []) | ||
| .concat((options.unignore || []).map(p => !p.startsWith('!') ? '!' + p : p)); | ||
| const push = (prefix, pattern) => { | ||
| const prev = result[result.length - 1]; | ||
| const type = prefix ? 'unignore' : 'ignore'; | ||
| if (prev && prev.type === type) { | ||
| if (!prev.patterns.includes(pattern)) { | ||
| prev.patterns.push(pattern); | ||
| } | ||
| } else { | ||
| result.push({ type, path: options.path || null, patterns: [pattern], index }); | ||
| index++; | ||
| } | ||
| }; | ||
| for (let pattern of patterns) { | ||
| let prefix = ''; | ||
| // An optional prefix "!" which negates the pattern; any matching file excluded by | ||
| // a previous pattern will become included again | ||
| if (pattern.startsWith('!')) { | ||
| pattern = pattern.slice(1); | ||
| prefix = '!'; | ||
| } | ||
| // add the raw pattern to the results | ||
| push(prefix, (pattern.startsWith('/') ? pattern.slice(1) : pattern)); | ||
| // add the glob pattern to the results | ||
| push(prefix, glob(pattern)); | ||
| } | ||
| return result; | ||
| }; | ||
| /** | ||
| * Expose `gitignore` | ||
| * Formats a .gitignore section | ||
| */ | ||
| module.exports = gitignore; | ||
| const formatSection = (section = {}) => { | ||
| const output = [section.comment || '']; | ||
| if (section.patterns?.length) { | ||
| output.push(section.patterns.join('\n')); | ||
| output.push(''); | ||
| } | ||
| return output.join('\n'); | ||
| }; | ||
| /** | ||
| * Format a .gitignore file from the given input or object from `.parse()`. | ||
| * @param {String} input File path or contents. | ||
| * @param {Object} options | ||
| * @return {String} Returns formatted string. | ||
| * @api public | ||
| */ | ||
| const format = (input, options = {}) => { | ||
| const parsed = parse(input, options); | ||
| const fn = options.formatSection || formatSection; | ||
| const sections = parsed.sections || parsed; | ||
| const output = []; | ||
| for (const section of [].concat(sections)) { | ||
| output.push(fn(section)); | ||
| } | ||
| return output.join('\n'); | ||
| }; | ||
| parse.file = parseFile; | ||
| parse.parse = parse; | ||
| parse.dedupe = dedupe; | ||
| parse.format = format; | ||
| parse.globs = globs; | ||
| parse.formatSection = formatSection; | ||
| parse.patterns = patterns; | ||
| /** | ||
| * Expose `parse` | ||
| */ | ||
| module.exports = parse; |
+5
-4
| { | ||
| "name": "parse-gitignore", | ||
| "description": "Parse a .gitignore or .npmignore file into an array of patterns.", | ||
| "version": "1.0.1", | ||
| "version": "2.0.0", | ||
| "homepage": "https://github.com/jonschlinkert/parse-gitignore", | ||
@@ -21,3 +21,3 @@ "author": "Jon Schlinkert (https://github.com/jonschlinkert)", | ||
| "engines": { | ||
| "node": ">=6" | ||
| "node": ">=14" | ||
| }, | ||
@@ -28,4 +28,5 @@ "scripts": { | ||
| "devDependencies": { | ||
| "gulp-format-md": "^1.0.0", | ||
| "mocha": "^5.2.0" | ||
| "gulp-format-md": "^2.0.0", | ||
| "matched": "^5.0.1", | ||
| "mocha": "^9.0.3" | ||
| }, | ||
@@ -32,0 +33,0 @@ "keywords": [ |
+3
-3
@@ -29,3 +29,3 @@ # parse-gitignore [](https://www.npmjs.com/package/parse-gitignore) [](https://npmjs.org/package/parse-gitignore) [](https://npmjs.org/package/parse-gitignore) [](https://travis-ci.org/jonschlinkert/parse-gitignore) | ||
| // pass the contents of a .gitignore file as a string or buffer | ||
| // pass the contents of a .gitignore file as a string or buffer | ||
| console.log(parse(fs.readFileSync('foo/bar/.gitignore'))); | ||
@@ -111,3 +111,3 @@ //=> ['*.DS_Store', 'node_modules', ...]; | ||
| | **Commits** | **Contributor** | | ||
| | **Commits** | **Contributor** | | ||
| | --- | --- | | ||
@@ -133,2 +133,2 @@ | 33 | [jonschlinkert](https://github.com/jonschlinkert) | | ||
| _This file was generated by [verb-generate-readme](https://github.com/verbose/verb-generate-readme), v0.6.0, on July 26, 2018._ | ||
| _This file was generated by [verb-generate-readme](https://github.com/verbose/verb-generate-readme), v0.6.0, on July 26, 2018._ |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
13476
68.85%194
361.9%132
0.76%3
50%3
200%1
Infinity%