Skip to content

Commit

Permalink
Merge pull request #109 from hildjj/cache
Browse files Browse the repository at this point in the history
Cache
  • Loading branch information
hildjj committed Oct 17, 2022
2 parents d56701d + 8a15406 commit 1607c00
Show file tree
Hide file tree
Showing 4 changed files with 471 additions and 168 deletions.
84 changes: 58 additions & 26 deletions README.md
Expand Up @@ -24,22 +24,62 @@ $ npm install -g editorconfig

## Usage

### in Node.js:

#### parse(filePath[, options])
### Options

options is an object with the following defaults:
Most of the API takes an `options` object, which has the following defaults:

```js
{
config: '.editorconfig',
version: pkg.version,
root: '/',
files: undefined
files: undefined,
cache: undefined,
};
```

Search for `.editorconfig` starting from the current directory to the root directory.
<dl>
<dt>config</dt>
<dd>The name of the config file to look for in the current and every parent
directory.</dd>

<dt>version</dt>
<dd>Which editorconfig spec version to use. Earlier versions had different
defaults.</dd>

<dt>root</dt>
<dd>What directory to stop processing in, even if we haven't found a file
containing root=true. Defaults to the root of the filesystem containing
`process.cwd()`.</dd>

<dt>files</dt>
<dd>Pass in an empty array, which will be filled with one object for each
config file processed. The objects will have the shape
`{filename: "[DIRECTORY]/.editorconfig", glob: "*"}`</dd>

<dt>cache</dt>
<dd>If you are going to process more than one file in the same project, pass
in a cache object. It must have `get(string): object|undefined` and
`set(string, object)` methods, like a JavaScript Map. A long-running
process might want to consider that this cache might grow over time,
and that the config files might change over time. However, we leave any
complexity of that nature to the caller, since there are so many different
approaches that might be taken based on latency, memory, and CPU trade-offs.
Note that some of the objects in the cache will be for files that did not
exist. Those objects will have a `notfound: true` property. All of the
objects will have a `name: string` property that contains the
fully-qualified file name of the config file and a `root: boolean` property
that describes if the config file had a `root=true` at the top. Any other
properties in the objects should be treated as opaque.</dd>
</dl>

### in Node.js:

#### parse(filePath[, options])

Search for `.editorconfig` files starting from the current directory to the
root directory. Combine all of the sections whose section names match
filePath into a single object.

Example:

Expand Down Expand Up @@ -69,33 +109,25 @@ const filePath = path.join(__dirname, 'sample.js');
*/
```

When the `files` option is an array, it will be filled with objects that
describe which .editorcofig files and glob section names contributed to the
returned configuration.

#### parseSync(filePath[, options])

Synchronous version of `editorconfig.parse()`.

#### parseString(fileContent)
#### parseBuffer(fileContent)

The `parse()` function above uses `parseString()` under the hood. If you have your file contents
just pass it to `parseString()` and it'll return the same results as `parse()`.
The `parse()` function above uses `parseBuffer()` under the hood. If you have
the contents of a config file, and want to see what is being processed for
just that file rather than the full directory hierarchy, this might be useful.

#### parseFromFiles(filePath, configs[, options])
#### parseString(fileContent)

options is an object with the following defaults:
This is a thin wrapper around `parseBuffer()` for backward-compatibility.
Prefer `parseBuffer()` to avoid an unnecessary UTF8-to-UTF16-to-UTF8
conversion. Deprecated.

```js
{
config: '.editorconfig',
version: pkg.version,
root: '/',
files: undefined
};
```
#### parseFromFiles(filePath, configs[, options])

Specify the `.editorconfig`.
Low-level interface, which exists only for backward-compatibility. Deprecated.

Example:

Expand All @@ -115,7 +147,7 @@ const configs = [
const filePath = path.join(__dirname, '/sample.js');

(async () => {
console.log(await editorconfig.parseFromFiles(filePath, configs))
console.log(await editorconfig.parseFromFiles(filePath, Promise.resolve(configs)))
})();
/*
{
Expand All @@ -132,7 +164,7 @@ const filePath = path.join(__dirname, '/sample.js');

#### parseFromFilesSync(filePath, configs[, options])

Synchronous version of `editorconfig.parseFromFiles()`.
Synchronous version of `editorconfig.parseFromFiles()`. Deprecated.

### in Command Line

Expand Down
42 changes: 34 additions & 8 deletions src/cli.ts
Expand Up @@ -4,11 +4,27 @@ import * as editorconfig from './'

import pkg from '../package.json'

/**
* Default output routine, goes to stdout.
*
* @param s String to output
*/
function writeStdOut(s: string): void {
process.stdout.write(s)
}

export default function cli(
/**
* Command line interface for editorconfig. Pulled out into a separate module
* to make it easier to test.
*
* @param args Usually process.argv. Note that the first two parameters are
* usually 'node' and 'editorconfig'
* @param testing If testing, you may pass in a Commander OutputConfiguration
* so that you can capture stdout and stderror. If `testing` is provided,
* this routine will throw an error instead of calling `process.exit`.
* @returns An array of combined properties, one for each file argument.
*/
export default async function cli(
args: string[],
testing?: OutputConfiguration
): Promise<editorconfig.Props[]> {
Expand Down Expand Up @@ -42,17 +58,27 @@ export default function cli(

const files = program.args
const opts = program.opts()
const cache = new Map<string, editorconfig.ProcessedFileConfig>()
const visited = opts.files ?
files.map<editorconfig.Visited[]>(() => []) :
undefined

return Promise.all(
files.map((filePath, i) => editorconfig.parse(filePath, {
config: opts.f as string,
version: opts.b as string,
files: visited ? visited[i] : undefined,
}))
).then((parsed) => {
// Process sequentially so caching works
async function processAll(): Promise<editorconfig.Props[]> {
const p = []
let i = 0
for (const filePath of files) {
p.push(await editorconfig.parse(filePath, {
config: opts.f as string,
version: opts.b as string,
files: visited ? visited[i++] : undefined,
cache,
}))
}
return p
}

return await processAll().then((parsed) => {
const header = parsed.length > 1
parsed.forEach((props, i) => {
if (header) {
Expand Down
55 changes: 55 additions & 0 deletions src/index.test.ts
Expand Up @@ -33,6 +33,24 @@ describe('parse', () => {
visited[0].glob.should.eql('*')
visited[0].fileName.should.endWith('.editorconfig')
})

it('caches', async () => {
const cache = new Map()
const cfg = await editorconfig.parse(target, {cache})
cfg.should.eql(expected)
cache.size.should.be.eql(2)
await editorconfig.parse(target, {cache})
cache.size.should.be.eql(2)
})

it('caches sync', () => {
const cache = new Map()
const cfg = editorconfig.parseSync(target, {cache})
cfg.should.eql(expected)
cache.size.should.be.eql(2)
editorconfig.parseSync(target, {cache})
cache.size.should.be.eql(2)
})
})

describe('parseFromFiles', () => {
Expand All @@ -55,6 +73,10 @@ describe('parseFromFiles', () => {
contents: fs.readFileSync(configPath),
})
const target = path.join(__dirname, '/app.js')
const configs2 = [
{ name: 'early', contents: Buffer.alloc(0) },
configs[0],
]

it('async', async () => {
const cfg: editorconfig.Props =
Expand All @@ -75,6 +97,34 @@ describe('parseFromFiles', () => {
cfg.should.eql({ foo: 'null' })
})

it('caches async', async () => {
const cache = new Map()
const cfg = await editorconfig.parseFromFiles(
target, Promise.resolve(configs2), {cache}
)
cfg.should.eql(expected)
cache.size.should.be.eql(2)
const cfg2 = await editorconfig.parseFromFiles(
target, Promise.resolve(configs2), {cache}
)
cfg2.should.eql(expected)
cache.size.should.be.eql(2)
})

it('caches sync', () => {
const cache = new Map()
const cfg = editorconfig.parseFromFilesSync(
target, configs2, {cache}
)
cfg.should.eql(expected)
cache.size.should.be.eql(2)
const cfg2 = editorconfig.parseFromFilesSync(
target, configs2, {cache}
)
cfg2.should.eql(expected)
cache.size.should.be.eql(2)
})

it('handles minimatch escapables', () => {
// Note that this `#` does not actually test the /^#/ escaping logic,
// because this path will go through a `path.dirname` before that happens.
Expand Down Expand Up @@ -122,4 +172,9 @@ describe('parseString', () => {
const cfg = editorconfig.parseString('root: ')
cfg.should.eql([[null, {}]])
})

it('handles backslashes in glob', () => {
const cfg = editorconfig.parseString('[a\\\\b]')
cfg.should.eql([[null, {}], ['a\\\\b', {}]])
})
})

0 comments on commit 1607c00

Please sign in to comment.