Skip to content

Commit

Permalink
Use dot notation for paths
Browse files Browse the repository at this point in the history
  • Loading branch information
ehmicky committed Jan 30, 2022
1 parent 5240af8 commit 0f37fb7
Show file tree
Hide file tree
Showing 5 changed files with 21 additions and 73 deletions.
10 changes: 1 addition & 9 deletions src/config/normalize/lib/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,18 +74,10 @@ const applyPropDefinition = async function ({

// Retrieve `opts` passed to most methods
const getOpts = function (name, config, context) {
const path = getPath(name)
const path = parse(name)
return { name, path, config, context }
}

const getPath = function (name) {
return parse(name).map(getPathKey)
}

const getPathKey = function ({ key }) {
return key
}

// When in `loose` mode, user errors are returned instead of being thrown.
// System errors are always propagated.
const handleError = function (error, loose) {
Expand Down
6 changes: 3 additions & 3 deletions src/config/normalize/lib/prop_path/entries.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ const listTokenEntries = function (entries, token) {
return entries.flatMap((entry) => getTokenEntries(entry, token))
}

const getTokenEntries = function ({ value, path }, { key, wildcard }) {
if (!wildcard) {
return [{ value: value[key], path: [...path, key] }]
const getTokenEntries = function ({ value, path }, token) {
if (token !== '*') {
return [{ value: value[token], path: [...path, token] }]
}

if (Array.isArray(value)) {
Expand Down
10 changes: 3 additions & 7 deletions src/config/normalize/lib/prop_path/get.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { listEntries } from './entries.js'
import { maybeParse } from './parse.js'
import { maybeParse, SEPARATOR } from './parse.js'

// Retrieve all properties in `target` matching a query string.
// The return value is an object where the key is the path to each value.
Expand All @@ -15,15 +15,11 @@ const normalizeEntry = function ({ value, path }) {
}

const serializeQuery = function (path) {
return path.reduce(appendKey, '')
return path.reduce(appendKey, '').slice(1)
}

const appendKey = function (pathStr, key) {
if (typeof key !== 'string') {
return `${pathStr}[${key}]`
}

return pathStr === '' ? `${pathStr}${key}` : `${pathStr}.${key}`
return `${pathStr}${SEPARATOR}${key}`
}

// Retrieve a single property's value in `target` matching a query string.
Expand Down
58 changes: 9 additions & 49 deletions src/config/normalize/lib/prop_path/parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@
// - this works better when setting multiple elements at once
// Syntax:
// - Dots are used for object properties, e.g. `one.two`
// - Brackets are used for array elements, e.g. `one[5]`
// - Dots and brackets can be used deeply, e.g. `one.two[5]`
// - Dots are also used for array elements, e.g. `one.5`
// - This can be used deeply, e.g. `one.two.5`
// - Wildcards are used with both objects and arrays to recurse over their
// children, e.g. `one.*` or `one[*]`.
// children, e.g. `one.*`
// - Empty keys are supported, e.g. `one.` for `{ one: { "": value } }`
// or `one..two` for `{ one: { "": { two: value } } }`
// - An empty string matches the root value
// We allow passing an array of tokens instead of a string with the above syntax
// - This is sometimes more convenient
// - Also, this allows property names to include special characters (dots,
Expand All @@ -25,54 +26,13 @@ export const maybeParse = function (queryOrTokens) {
}

export const parse = function (query) {
if (query === '') {
return []
}

const normalizedQuery = prependDot(query)
const matchResults = [...normalizedQuery.matchAll(QUERY_REGEXP)]
validateQuery(matchResults, query, normalizedQuery)
const tokens = matchResults.map(getToken)
return tokens
}

// Queries can start with an optional dot.
const prependDot = function (query) {
const [firstChar] = query
return firstChar !== '.' && firstChar !== '[' ? `.${query}` : query
}

const QUERY_REGEXP =
/((\.(?<name>([^.[\]*\d][^.[\]*]*|(?<nameWildcard>\*)|((?=[.[]|$)))))|(\[(?<index>([\d]+|(?<indexWildcard>\*)))\]))/guy

// Validate against syntax errors in the query
// TODO: add more error messages for common mistakes
const validateQuery = function (matchResults, query, normalizedQuery) {
const matchedQuery = matchResults.map(getMatch).join('')

if (matchedQuery !== normalizedQuery) {
throw new Error(
`Syntax error in path "${query}" (starting at index ${matchedQuery.length})`,
)
}
return query === '' ? [] : query.split(SEPARATOR).map(getToken)
}

const getMatch = function ([match]) {
return match
}
export const SEPARATOR = '.'

const getToken = function ({
groups: { name, nameWildcard, index, indexWildcard },
}) {
const key = getKey(name, index)
const wildcard = nameWildcard !== undefined || indexWildcard !== undefined
return { key, wildcard }
const getToken = function (token) {
return POSITIVE_INTEGER_REGEXP.test(token) ? Number(token) : token
}

const getKey = function (name, index) {
if (name !== undefined) {
return name
}

return index === '*' ? index : Number(index)
}
const POSITIVE_INTEGER_REGEXP = /^\d+$/u
10 changes: 5 additions & 5 deletions src/config/normalize/prop_defs.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ const configProp = {
}

const configAny = {
name: 'config[*]',
name: 'config.*',
pick: amongCommands(['dev', 'remove', 'run', 'show']),
validate: validateDefinedString,
}
Expand Down Expand Up @@ -179,7 +179,7 @@ const reporter = {
}

const reporterAny = {
name: 'reporter[*]',
name: 'reporter.*',
pick: amongCommands(['remove', 'run', 'show']),
validate: validateDefinedString,
}
Expand All @@ -204,7 +204,7 @@ const runner = {
}

const runnerAny = {
name: 'runner[*]',
name: 'runner.*',
pick: amongCommands(['dev', 'run']),
validate: validateDefinedString,
}
Expand All @@ -231,7 +231,7 @@ const select = {
}

const selectAny = {
name: 'select[*]',
name: 'select.*',
pick: amongCommands(['dev', 'remove', 'run', 'show']),
validate: validateString,
}
Expand Down Expand Up @@ -297,7 +297,7 @@ const tasks = {
}

const tasksAny = {
name: 'tasks[*]',
name: 'tasks.*',
pick: amongCommands(['dev', 'run']),
validate: validateDefinedString,
async transform(value, { name, context: { configInfos } }) {
Expand Down

0 comments on commit 0f37fb7

Please sign in to comment.