Skip to content

Commit 1b424df

Browse files
authored
Bring in data-directory, let's go async file reads (#16782)
* Bring in data-directory, let's go async file reads * Lint fixes * Update glossary.js
1 parent b807035 commit 1b424df

11 files changed

Lines changed: 166 additions & 42 deletions

File tree

lib/data-directory.js

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
const assert = require('assert')
2+
const fs = require('fs').promises
3+
const path = require('path')
4+
const walk = require('walk-sync')
5+
const yaml = require('js-yaml')
6+
const { isRegExp, set } = require('lodash')
7+
const filenameToKey = require('./filename-to-key')
8+
9+
module.exports = async function dataDirectory (dir, opts = {}) {
10+
const defaultOpts = {
11+
preprocess: (content) => { return content },
12+
ignorePatterns: [/README\.md$/i],
13+
extensions: [
14+
'.json',
15+
'.md',
16+
'.markdown',
17+
'.yaml',
18+
'.yml'
19+
]
20+
}
21+
22+
opts = Object.assign({}, defaultOpts, opts)
23+
24+
// validate input
25+
assert(Array.isArray(opts.ignorePatterns))
26+
assert(opts.ignorePatterns.every(isRegExp))
27+
assert(Array.isArray(opts.extensions))
28+
assert(opts.extensions.length)
29+
30+
// start with an empty data object
31+
const data = {}
32+
33+
// find YAML and Markdown files in the given directory, recursively
34+
await Promise.all(walk(dir, { includeBasePath: true })
35+
.filter(filename => {
36+
// ignore files that match any of ignorePatterns regexes
37+
if (opts.ignorePatterns.some(pattern => pattern.test(filename))) return false
38+
39+
// ignore files that don't have a whitelisted file extension
40+
return opts.extensions.includes(path.extname(filename).toLowerCase())
41+
})
42+
.map(async filename => {
43+
// derive `foo.bar.baz` object key from `foo/bar/baz.yml` filename
44+
const key = filenameToKey(path.relative(dir, filename))
45+
const extension = path.extname(filename).toLowerCase()
46+
47+
let fileContent = await fs.readFile(filename, 'utf8')
48+
49+
if (opts.preprocess) fileContent = opts.preprocess(fileContent)
50+
51+
// add this file's data to the global data object
52+
switch (extension) {
53+
case '.json':
54+
set(data, key, JSON.parse(fileContent))
55+
break
56+
case '.yaml':
57+
case '.yml':
58+
set(data, key, yaml.safeLoad(fileContent, { filename }))
59+
break
60+
case '.md':
61+
case '.markdown':
62+
set(data, key, fileContent)
63+
break
64+
}
65+
}))
66+
67+
return data
68+
}

lib/filename-to-key.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/* eslint-disable prefer-regex-literals */
2+
const path = require('path')
3+
const { escapeRegExp } = require('lodash')
4+
5+
// slash at the beginning of a filename
6+
const leadingPathSeparator = new RegExp(`^${escapeRegExp(path.sep)}`)
7+
const windowsLeadingPathSeparator = new RegExp('^/')
8+
9+
// all slashes in the filename. path.sep is OS agnostic (windows, mac, etc)
10+
const pathSeparator = new RegExp(escapeRegExp(path.sep), 'g')
11+
const windowsPathSeparator = new RegExp('/', 'g')
12+
13+
// handle MS Windows style double-backslashed filenames
14+
const windowsDoubleSlashSeparator = new RegExp('\\\\', 'g')
15+
16+
// derive `foo.bar.baz` object key from `foo/bar/baz.yml` filename
17+
module.exports = function filenameToKey (filename) {
18+
const extension = new RegExp(`${path.extname(filename)}$`)
19+
const key = filename
20+
.replace(extension, '')
21+
.replace(leadingPathSeparator, '')
22+
.replace(windowsLeadingPathSeparator, '')
23+
.replace(pathSeparator, '.')
24+
.replace(windowsPathSeparator, '.')
25+
.replace(windowsDoubleSlashSeparator, '.')
26+
27+
return key
28+
}

lib/site-data.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ const path = require('path')
22
const flat = require('flat')
33
const { get, set } = require('lodash')
44
const languages = require('./languages')
5-
const dataDirectory = require('@github-docs/data-directory')
5+
const dataDirectory = require('./data-directory')
66
const encodeBracketedParentheticals = require('./encode-bracketed-parentheticals')
77

8-
const loadSiteDataFromDir = dir => ({
8+
const loadSiteDataFromDir = async dir => ({
99
site: {
10-
data: dataDirectory(path.join(dir, 'data'), {
10+
data: await dataDirectory(path.join(dir, 'data'), {
1111
preprocess: dataString =>
1212
encodeBracketedParentheticals(dataString.trimEnd()),
1313
ignorePatterns: [/README\.md$/]
@@ -18,15 +18,15 @@ const loadSiteDataFromDir = dir => ({
1818
module.exports = async function loadSiteData () {
1919
// load english site data
2020
const siteData = {
21-
en: loadSiteDataFromDir(languages.en.dir)
21+
en: await loadSiteDataFromDir(languages.en.dir)
2222
}
2323

2424
// load and add other language data to siteData where keys match english keys,
2525
// filling holes with english site data
2626
const englishKeys = Object.keys(flat(siteData.en))
2727
for (const language of Object.values(languages)) {
2828
if (language.code === 'en') continue
29-
const data = loadSiteDataFromDir(language.dir)
29+
const data = await loadSiteDataFromDir(language.dir)
3030
for (const key of englishKeys) {
3131
set(
3232
siteData,

package-lock.json

Lines changed: 6 additions & 36 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
"@babel/preset-env": "^7.12.7",
2020
"@babel/preset-react": "^7.12.7",
2121
"@babel/runtime": "^7.11.2",
22-
"@github-docs/data-directory": "^1.2.0",
2322
"@github-docs/frontmatter": "^1.3.1",
2423
"@graphql-inspector/core": "^2.3.0",
2524
"@graphql-tools/load": "^6.2.5",
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
const filenameToKey = require('../../../lib/filename-to-key')
2+
3+
describe('filename-to-key', () => {
4+
test('converts filenames to object keys', () => {
5+
expect(filenameToKey('foo/bar/baz.txt')).toBe('foo.bar.baz')
6+
})
7+
8+
test('ignores leading slash on filenames', () => {
9+
expect(filenameToKey('/foo/bar/baz.txt')).toBe('foo.bar.baz')
10+
})
11+
12+
test('supports MS Windows paths', () => {
13+
expect(filenameToKey('path\\to\\file.txt')).toBe('path.to.file')
14+
})
15+
})
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
I am a README. I am ignored by default.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
another_markup_language: 'yes'
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"meaningOfLife": 42}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
I am markdown!

0 commit comments

Comments
 (0)