Skip to content

Commit

Permalink
Allow path to be sync
Browse files Browse the repository at this point in the history
  • Loading branch information
ehmicky committed May 29, 2022
1 parent f65d4ac commit 8198973
Show file tree
Hide file tree
Showing 4 changed files with 148 additions and 41 deletions.
78 changes: 64 additions & 14 deletions src/config/normalize/lib/keywords/list/path/access.js
Original file line number Diff line number Diff line change
@@ -1,50 +1,100 @@
import { constants } from 'fs'
import { constants, accessSync } from 'fs'
import { access } from 'fs/promises'

import { READ_KEYWORD, WRITE_KEYWORD, EXEC_KEYWORD } from './normalize.js'

// Check the "read|write|execute" keywords
export const validateAccess = async function (input, keywords) {
const accesses = ACCESS_METHODS.filter(({ keyword }) => keywords.has(keyword))
export const validateAccess = function (input, keywords) {
const accesses = listAccesses(keywords)

if (accesses.length !== 0 && !(await hasValidAccess(input, accesses))) {
const invalidAccesses = await listInvalidAccesses(input, accesses)
throw new Error(`must be ${invalidAccesses}.`)
if (accesses.length !== 0 && !hasValidAccess(input, accesses)) {
const invalidAccesses = listInvalidAccesses(input, accesses)
throwAccessError(invalidAccesses)
}
}

export const validateAccessAsync = async function (input, keywords) {
const accesses = listAccesses(keywords)

if (accesses.length !== 0 && !(await hasValidAccessAsync(input, accesses))) {
const invalidAccesses = await listInvalidAccessesAsync(input, accesses)
throwAccessError(invalidAccesses)
}
}

const listAccesses = function (keywords) {
return ACCESS_METHODS.filter(({ keyword }) => keywords.has(keyword))
}

const ACCESS_METHODS = [
{ keyword: READ_KEYWORD, flag: constants.R_OK, name: 'readable' },
{ keyword: WRITE_KEYWORD, flag: constants.W_OK, name: 'writable' },
{ keyword: EXEC_KEYWORD, flag: constants.X_OK, name: 'executable' },
]

const hasValidAccess = async function (input, accesses) {
const flags = accesses.reduce(orFlag, constants.F_OK)
return await checkAccess(input, flags)
const hasValidAccess = function (input, accesses) {
const flags = listFlags(accesses)
return checkAccess(input, flags)
}

const hasValidAccessAsync = async function (input, accesses) {
const flags = listFlags(accesses)
return await checkAccessAsync(input, flags)
}

const listFlags = function (accesses) {
return accesses.reduce(orFlag, constants.F_OK)
}

const orFlag = function (flags, { flag }) {
// eslint-disable-next-line no-bitwise
return flags | flag
}

const listInvalidAccesses = async function (input, accesses) {
const listInvalidAccesses = function (input, accesses) {
const names = accesses.map(({ flag, name }) =>
getInvalidAccess(input, flag, name),
)
return joinAccessNames(names)
}

const listInvalidAccessesAsync = async function (input, accesses) {
const names = await Promise.all(
accesses.map(({ flag, name }) => getInvalidAccess(input, flag, name)),
accesses.map(({ flag, name }) => getInvalidAccessAsync(input, flag, name)),
)
return joinAccessNames(names)
}

const getInvalidAccess = function (input, flag, name) {
return checkAccess(input, flag) ? undefined : name
}

const getInvalidAccessAsync = async function (input, flag, name) {
return (await checkAccessAsync(input, flag)) ? undefined : name
}

const joinAccessNames = function (names) {
return names.filter(Boolean).join(' and ')
}

const getInvalidAccess = async function (input, flag, name) {
return (await checkAccess(input, flag)) ? undefined : name
export const checkAccess = function (input, flags) {
try {
accessSync(input, flags)
return true
} catch {
return false
}
}

export const checkAccess = async function (input, flags) {
export const checkAccessAsync = async function (input, flags) {
try {
await access(input, flags)
return true
} catch {
return false
}
}

const throwAccessError = function (invalidAccesses) {
throw new Error(`must be ${invalidAccesses}.`)
}
10 changes: 7 additions & 3 deletions src/config/normalize/lib/keywords/list/path/exist.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import { constants } from 'fs'

import { checkAccess } from './access.js'
import { checkAccess, checkAccessAsync } from './access.js'
import { EXIST_KEYWORD } from './normalize.js'

// Check if a file exists
export const fileExists = async function (input) {
return await checkAccess(input, constants.F_OK)
export const fileExists = function (input) {
return checkAccess(input, constants.F_OK)
}

export const fileExistsAsync = async function (input) {
return await checkAccessAsync(input, constants.F_OK)
}

// Fail if a file does not exist and the "exist" keyword was used
Expand Down
41 changes: 31 additions & 10 deletions src/config/normalize/lib/keywords/list/path/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,46 @@ import { resolve } from 'path'

import { validateDefinedString } from '../../normalize/common.js'

import { validateAccess } from './access.js'
import { fileExists, validateExists } from './exist.js'
import { validateAccess, validateAccessAsync } from './access.js'
import { fileExists, fileExistsAsync, validateExists } from './exist.js'
import { normalize, EXAMPLE_KEYWORDS } from './normalize.js'
import { validateType } from './type.js'
import { validateType, validateTypeAsync } from './type.js'

const main = function (keywords, input, { cwd }) {
const inputA = normalizeInput(input, cwd)
validateFile(inputA, keywords)
return { input: inputA }
}

const mainAsync = async function (keywords, input, { cwd }) {
validateDefinedString(input)
const inputA = resolve(cwd, input)
await validateFile(inputA, keywords)
const inputA = normalizeInput(input, cwd)
await validateFileAsync(inputA, keywords)
return { input: inputA }
}

const validateFile = async function (input, keywords) {
if (!(await fileExists(input))) {
const normalizeInput = function (input, cwd) {
validateDefinedString(input)
return resolve(cwd, input)
}

const validateFile = function (input, keywords) {
if (!fileExists(input)) {
validateExists(keywords)
return
}

validateType(input, keywords)
validateAccess(input, keywords)
}

const validateFileAsync = async function (input, keywords) {
if (!(await fileExistsAsync(input))) {
validateExists(keywords)
return
}

await validateType(input, keywords)
await validateAccess(input, keywords)
await validateTypeAsync(input, keywords)
await validateAccessAsync(input, keywords)
}

// Apply `path[(input, info)]` which resolves the input as an absolute file path
Expand All @@ -38,5 +58,6 @@ export default {
hasInput: true,
exampleDefinition: EXAMPLE_KEYWORDS,
normalize,
main,
mainAsync,
}
60 changes: 46 additions & 14 deletions src/config/normalize/lib/keywords/list/path/type.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,63 @@
import { statSync } from 'fs'
import { stat } from 'fs/promises'

import { FILE_KEYWORD, DIR_KEYWORD } from './normalize.js'

// Check the "file|directory" keywords
export const validateType = async function (input, keywords) {
const types = TYPE_METHODS.filter(({ keyword }) => keywords.has(keyword))
export const validateType = function (input, keywords) {
const types = listTypes(keywords)

if (types.length !== 0 && !(await hasValidType(input, types))) {
const message = types.map(({ name }) => name).join(' or ')
throw new Error(`must be ${message}.`)
if (types.length !== 0 && !hasValidType(input, types)) {
throwTypeError(types)
}
}

const hasValidType = async function (input, types) {
const fileStat = await getFileStat(input)
return (
fileStat !== undefined && types.some(({ method }) => fileStat[method]())
)
export const validateTypeAsync = async function (input, keywords) {
const types = listTypes(keywords)

if (types.length !== 0 && !(await hasValidTypeAsync(input, types))) {
throwTypeError(types)
}
}

const getFileStat = async function (input) {
try {
return await stat(input)
} catch {}
const listTypes = function (keywords) {
return TYPE_METHODS.filter(({ keyword }) => keywords.has(keyword))
}

const TYPE_METHODS = [
{ keyword: FILE_KEYWORD, method: 'isFile', name: 'a regular file' },
{ keyword: DIR_KEYWORD, method: 'isDirectory', name: 'a directory' },
]

const hasValidType = function (input, types) {
const fileStat = getFileStat(input)
return hasValidFileStat(fileStat, types)
}

const hasValidTypeAsync = async function (input, types) {
const fileStat = await getFileStatAsync(input)
return hasValidFileStat(fileStat, types)
}

const getFileStat = function (input) {
try {
return statSync(input)
} catch {}
}

const getFileStatAsync = async function (input) {
try {
return await stat(input)
} catch {}
}

const hasValidFileStat = function (fileStat, types) {
return (
fileStat !== undefined && types.some(({ method }) => fileStat[method]())
)
}

const throwTypeError = function (types) {
const message = types.map(({ name }) => name).join(' or ')
throw new Error(`must be ${message}.`)
}

0 comments on commit 8198973

Please sign in to comment.