parse-gitignore
Advanced tools
Comparing version 1.0.1 to 2.0.0
244
index.js
/*! | ||
* 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; |
{ | ||
"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": [ |
@@ -29,3 +29,3 @@ # parse-gitignore [![NPM version](https://img.shields.io/npm/v/parse-gitignore.svg?style=flat)](https://www.npmjs.com/package/parse-gitignore) [![NPM monthly downloads](https://img.shields.io/npm/dm/parse-gitignore.svg?style=flat)](https://npmjs.org/package/parse-gitignore) [![NPM total downloads](https://img.shields.io/npm/dt/parse-gitignore.svg?style=flat)](https://npmjs.org/package/parse-gitignore) [![Linux Build Status](https://img.shields.io/travis/jonschlinkert/parse-gitignore.svg?style=flat&label=Travis)](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
13476
194
132
3
2
1