Skip to content

Commit

Permalink
refactor: Do some cleaning and normalizing
Browse files Browse the repository at this point in the history
  • Loading branch information
fabschurt committed May 6, 2024
1 parent 79da07a commit 8f18716
Show file tree
Hide file tree
Showing 12 changed files with 75 additions and 73 deletions.
9 changes: 3 additions & 6 deletions src/adapter/icu.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
import assert from 'node:assert'

export const translateString = (
(IntlMessageFormat, dictionary, defaultLocale = 'en') => (
(msgID, params = {}, locale = defaultLocale) => {
assert(
msgID in dictionary,
`The messsage with ID \`${msgID}\` does not exist in the dictionary.`,
)
if (!(msgID in dictionary)) {
throw new RangeError(`The messsage with ID \`${msgID}\` does not exist in the dictionary.`)
}

return new IntlMessageFormat(dictionary[msgID], locale).format(params)
}
Expand Down
10 changes: 4 additions & 6 deletions src/domain/data.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
const DATA_FILE_PATH = 'data.json'
const ENV_VAR_PLACEHOLDER_PATTERN = /^%(.+)%$/

const captureEnvVarName = (value) => {
const match = value.match?.(ENV_VAR_PLACEHOLDER_PATTERN)

return match ? match[1] : null
}
const captureEnvVarName = (value) => value.match?.(ENV_VAR_PLACEHOLDER_PATTERN)?.[1] ?? null

export const parseProjectData = (parseJSONFile, withSrcDir) => (
withSrcDir((prefixWithSrcDir) => parseJSONFile(prefixWithSrcDir(DATA_FILE_PATH)))
withSrcDir(
(prefixWithSrcDir) => parseJSONFile(prefixWithSrcDir(DATA_FILE_PATH))
)
)

export const mergeDataWithEnvVars = (
Expand Down
5 changes: 3 additions & 2 deletions src/domain/i18n.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import assert from 'node:assert'
import { join } from 'node:path'

const LANG_PATTERN = /^[a-z]{2}$/
Expand All @@ -8,7 +7,9 @@ const TRANSLATION_FILE_EXT = '.json'
export const parseProjectTranslations = (
(parseJSONFile, withSrcDir, dotFlattenObject) => (
(lang) => {
assert.match(lang, LANG_PATTERN, `\`${lang}\` is not a valid language code.`)
if (!LANG_PATTERN.test(lang)) {
throw new Error(`\`${lang}\` is not a valid language code.`)
}

return withSrcDir((prefixWithSrcDir) => {
const translationFilePath = join(
Expand Down
3 changes: 1 addition & 2 deletions src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,13 @@ export default async function main(
lang = null,
envVars = {},
) {
const _parseJSONFile = parseJSONFile(ifPathExists, readFile)

await ifPathExists(buildDirPath, rmDir)

const [withSrcDir, withBuildDir] = await Promise.all([
withDir(srcDirPath),
withScratchDir(buildDirPath),
])
const _parseJSONFile = parseJSONFile(ifPathExists, readFile)

return (
Promise.all([
Expand Down
15 changes: 7 additions & 8 deletions src/utils/fs.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import assert from 'node:assert'
import * as fs from 'node:fs/promises'
import { join } from 'node:path'

const prefixWithDir = (dirPath) => (relativePath = '') => join(dirPath, relativePath)

export const withDir = (
(dirPath) => (
async (cb) => {
await fs.access(dirPath)

const prefixWithDir = (relativePath = '') => join(dirPath, relativePath)

return cb(prefixWithDir)
return cb(prefixWithDir(dirPath))
}
)
)
Expand All @@ -29,9 +28,7 @@ export const withScratchDir = (

await fs.access(dirPath, fs.constants.W_OK)

const prefixWithDir = (relativePath = '') => join(dirPath, relativePath)

return cb(prefixWithDir)
return cb(prefixWithDir(dirPath))
}
)
)
Expand Down Expand Up @@ -59,7 +56,9 @@ export const copyDir = (srcPath, destPath) => (
)

export const rmDir = (path) => {
assert.notStrictEqual(path, '/', 'Removing the filesystem’s root is not allowed.')
if (path === '/') {
throw new SystemError('Removing the filesystem’s root is not allowed.')
}

return fs.rm(path, { recursive: true })
}
6 changes: 3 additions & 3 deletions src/utils/json.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
export const parseJSONFile = (
(ifPathExists, readFile) => (
(filePath) => (
(jsonFilePath) => (
ifPathExists(
filePath,
() => readFile(filePath).then(JSON.parse),
jsonFilePath,
(filePath) => readFile(filePath).then(JSON.parse),
{},
)
)
Expand Down
40 changes: 19 additions & 21 deletions src/utils/object.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,14 @@
import assert from 'node:assert'
const PROP_PATH_PATTERN = /^(?<currentKey>[a-z_]+)(?<rest>(?:\.[a-z_]+)*)$/i

const valueIsObject = (value) => (
typeof value !== 'undefined' &&
value.constructor.name === 'Object'
)
const valueIsObject = (value) => value.constructor?.name === 'Object'

const valueIsArray = (value) => (
typeof value !== 'undefined' &&
value.constructor.name === 'Array'
)
const valueIsArray = (value) => value.constructor?.name === 'Array'

const valueIsComposite = (value) => valueIsObject(value) || valueIsArray(value)

const testPropPathValidity = (propPath) => (
/^(?<currentKey>[a-z_]+)(?<rest>(?:\.[a-z_]+)*)$/i
.exec(propPath)
)
const objectIsEmpty = (obj) => Object.keys(obj).length > 0

const matchPropPath = (propPath) => propPath.match?.(PROP_PATH_PATTERN) ?? null

export const deepCloneObject = (obj) => JSON.parse(JSON.stringify(obj))

Expand All @@ -35,37 +28,42 @@ export const transformObjectValues = (obj, cb) => {
return obj
}

export const cleanUpObjectList = (objectList) => objectList.filter((obj) => Object.keys(obj).length)
export const cleanUpObjectList = (objectList) => objectList.filter(objectIsEmpty)

export const mergeObjectList = (objectList) => Object.assign({}, ...objectList)

export const accessObjectProp = (obj, propPath) => {
const match = testPropPathValidity(propPath)
const match = matchPropPath(propPath)

assert.notStrictEqual(match, null, `The property path \`${propPath}\` is invalid.` )
if (match === null) {
throw new Error(`The property path \`${propPath}\` is invalid.` )
}

const currentKey = match.groups.currentKey

if (typeof obj[currentKey] === 'undefined') {
if (!(currentKey in obj)) {
throw new RangeError(`The key \`${currentKey}\` does not exist in the current object tree.`)
}

const rest = match.groups.rest
const hasRest = Boolean(rest.length)
const currentValue = obj[currentKey]

if (hasRest && !valueIsObject(obj[currentKey])) {
if (hasRest && !valueIsObject(currentValue)) {
throw new TypeError(`The value at key \`${currentKey}\` is not an object and can’t be traversed.`)
}

return (
hasRest
? accessObjectProp(obj[currentKey], rest.substring(1))
: obj[currentKey]
? accessObjectProp(currentValue, rest.substring(1))
: currentValue
)
}

export const dotFlattenObject = (obj) => {
assert(valueIsComposite(obj), 'Only composite values can be flattened.')
if (!valueIsComposite(obj)) {
throw new TypeError('Only composite values can be flattened.')
}

const output = {}

Expand Down
2 changes: 1 addition & 1 deletion tests/adapter/icu.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ describe('#src/adapter/icu', () => {
occupation: 'I’m a {job}, the best in {town}.',
number_of_kids: 'I have {kidNum, plural, =0 {no kids} =1 {a single child} other {# kids}}.',
bastille_day: 'France’s Bastille Day was on {bastilleDay, date, ::yyyyMMMMdd}.',
account_balance: 'I have {balance, number} moneyz on my bank account.'
account_balance: 'I have {balance, number} moneyz on my bank account.',
}

const trans = translateString(IntlMessageFormat, dictionary)
Expand Down
8 changes: 5 additions & 3 deletions tests/domain/data.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { parseProjectData, mergeDataWithEnvVars } from '#src/domain/data'

describe('#src/domain/data', () => {
describe('parseProjectData()', () => {
it('parses data from a `data.json` file at project root', async () => {
it('parses data from a `data.json` file at the project root', async () => {
await withTempDir(async (prefixWithTempDir) => {
const srcDirPath = prefixWithTempDir('src')
const dataFilePath = join(srcDirPath, 'data.json')
Expand Down Expand Up @@ -46,7 +46,7 @@ describe('#src/domain/data', () => {
42,
'%SECRET_PHONE_NUMBER%',
false,
'LANG'
'LANG',
],
},
cruft: {
Expand All @@ -55,6 +55,7 @@ describe('#src/domain/data', () => {
lang: '%LANG%',
phone: 'SECRET_PHONE_NUMBER',
},
nil: '%NOPE%',
},
}

Expand All @@ -74,7 +75,7 @@ describe('#src/domain/data', () => {
42,
'+33700000000',
false,
'LANG'
'LANG',
],
},
cruft: {
Expand All @@ -83,6 +84,7 @@ describe('#src/domain/data', () => {
lang: 'fr',
phone: 'SECRET_PHONE_NUMBER',
},
nil: null,
},
},
)
Expand Down
11 changes: 3 additions & 8 deletions tests/domain/i18n.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,10 @@ describe('#src/domain/i18n', () => {
}
`)

const withSrcDir = withDir(srcDirPath)
const _parseJSONFile = parseJSONFile(ifPathExists, readFile)
const _parseProjectTranslations = (
parseProjectTranslations(
_parseJSONFile,
withSrcDir,
parseJSONFile(ifPathExists, readFile),
withDir(srcDirPath),
dotFlattenObject,
)
)
Expand Down Expand Up @@ -83,10 +81,7 @@ describe('#src/domain/i18n', () => {
})

it('throws if an invalid `lang` parameter is passed', () => {
assert.throws(
() => parseProjectTranslations(noop, noop, noop)('fAiL'),
assert.AssertionError,
)
assert.throws(() => parseProjectTranslations(noop, noop, noop)('fAiL'))
})
})
})
8 changes: 6 additions & 2 deletions tests/utils/fs.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@ describe('#src/utils/fs', () => {
const nonExistentPath = generateNonExistentPath()
let marker = 0

await ifPathExists('/', () => marker = 1)
await ifPathExists(nonExistentPath, () => marker = 2)
await ifPathExists('/', (_) => marker = 1)
await ifPathExists(nonExistentPath, (_) => marker = 2)

assert.strictEqual(marker, 1)
})
Expand Down Expand Up @@ -185,5 +185,9 @@ describe('#src/utils/fs', () => {
await assert.rejects(fs.access(dirPath))
})
})

it('prevents the deletion of the filesystem’s root', {
skip: 'This is too scary to test 😱 (disk wipe hazard).',
})
})
})
31 changes: 20 additions & 11 deletions tests/utils/object.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,10 @@ describe('#src/utils/object', () => {
describe('transformObjectValues()', () => {
it('recursively passes each of an object’s nested values trough a callback', () => {
const obj = {
foo: 'bar',
foo: 42,
stuff: [
'foo',
'bar',
Symbol.for('clutter'),
{
some_prop: 'a',
other_prop: 'b',
Expand All @@ -90,10 +90,10 @@ describe('#src/utils/object', () => {
assert.deepStrictEqual(
transformObjectValues(obj, transformer),
{
foo: 'BAR',
foo: 42,
stuff: [
'FOO',
'BAR',
Symbol.for('clutter'),
{
some_prop: 'A',
other_prop: 'B',
Expand All @@ -112,6 +112,17 @@ describe('#src/utils/object', () => {
},
)
})

it('throws if not passed a composite value', () => {
assert.throws(
() => dotFlattenObject(3.14),
TypeError,
)
assert.throws(
() => dotFlattenObject(Symbol.for('foobar')),
TypeError,
)
})
})

describe('cleanUpObjectList()', () => {
Expand All @@ -127,6 +138,7 @@ describe('#src/utils/object', () => {
{
stuff: 'clutter',
},
{},
]

assert.deepStrictEqual(
Expand Down Expand Up @@ -170,7 +182,7 @@ describe('#src/utils/object', () => {
identity: {
name: 'John',
surname: 'Doe',
}
},
}
)
})
Expand All @@ -192,10 +204,7 @@ describe('#src/utils/object', () => {
})

it('throws if the property path is invalid', () => {
assert.throws(
() => accessObjectProp({}, '379&239--'),
assert.AssertionError,
)
assert.throws(() => accessObjectProp({}, '379&239--'))
})

it('throws if one of the nested properties does not exist or is not an object', () => {
Expand Down Expand Up @@ -248,11 +257,11 @@ describe('#src/utils/object', () => {
it('throws if not passed a composite value', () => {
assert.throws(
() => dotFlattenObject(42),
assert.AssertionError,
TypeError,
)
assert.throws(
() => dotFlattenObject('Hello!'),
assert.AssertionError,
TypeError,
)
})
})
Expand Down

0 comments on commit 8f18716

Please sign in to comment.