Last active
February 16, 2024 01:51
-
-
Save nyteshade/7b7f101c022ff98829be54d0c70200f6 to your computer and use it in GitHub Desktop.
Read macos `mdls` content as JSON or nicely printed in terminal
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
convertFonts() { | |
if [[ ${#} -lt 1 ]]; then | |
echo "Usage: convertFonts <startDir> <outDir> [rename]" | |
echo "where" | |
echo " outDir - if missing, this is the same as startDir" | |
echo "\n\x1b[3mIf rename is \x1b[1mtrue\x1b[22m, old file name is mv'ed to the new name" | |
echo "otherwise a copy is made and the original is untouched.\x1b[23m" | |
else | |
startDir="${1:-.}" | |
outDir="${startDir}" | |
# Initialize rename to a default value (false or true, depending on your needs) | |
rename=false | |
if [[ $# -gt 2 ]]; then | |
# More than two arguments, check the last argument for 'true' | |
if [[ ${@: -1} = true ]]; then | |
rename=true | |
fi | |
# Assign the second to last argument to outDir | |
outDir="${@: -2:1}" | |
elif [[ $# -eq 2 ]]; then | |
# Exactly two arguments | |
if [[ ${@: -1} = true ]] || [[ ${@: -1} = false ]]; then | |
# If the last argument is explicitly true or false, assign it to rename | |
rename=${@: -1} | |
else | |
# Otherwise, assume it's the output directory | |
outDir="${@: -1}" | |
fi | |
fi | |
echo "First, true type fonts..." | |
for f in ${startDir}/**/*.ttf; do | |
mkdir -p "${outDir}/$(dirname ${f})" | |
out="${outDir}/$(dirname ${f})/$(mdls-json ${f} com_apple_ats_name_postscript reduce json)" | |
out="${out//\"/}.ttf" | |
rmMsg=" \x1b[1m[\x1b[22;33mcopied\x1b[39;1m]\x1b[22m" | |
if [[ -e "${out}" ]]; then | |
rmMsg=" \x1b[1m[\x1b[22;34mskipped - already exists\x1b[39;1m]\x1b[22m" | |
else | |
if [[ ${2:-false} = true ]]; then | |
mv "${f}" "${out}" | |
rmMsg=" \x1b[1m[\x1b[22;32mrenamed\x1b[39;1m]\x1b[22m" | |
else | |
cp -fp "${f}" "${out}" | |
fi | |
fi | |
echo " ${f} -> ${out//\"/}${rmMsg}" | |
done | |
echo "\nNext, open type fonts..." | |
for f in ${1:-.}/**/*.otf; do | |
mkdir -p "${outDir}/$(dirname ${f})" | |
out="${outDir}/$(dirname ${f})/$(mdls-json ${f} com_apple_ats_name_postscript reduce json)" | |
out="${out//\"/}.otf" | |
rmMsg=" \x1b[1m[\x1b[22;33mcopied\x1b[39;1m]\x1b[22m" | |
if [[ -e "${out}" ]]; then | |
rmMsg=" \x1b[1m[\x1b[22;34mskipped - already exists\x1b[39;1m]\x1b[22m" | |
else | |
if [[ ${2:-false} = true ]]; then | |
mv "${f}" "${out}" | |
rmMsg=" \x1b[1m[\x1b[22;32mrenamed\x1b[39;1m]\x1b[22m" | |
else | |
cp -fp "${f}" "${out}" | |
fi | |
fi | |
echo " ${f} -> ${out//\"/}${rmMsg}" | |
done | |
fi | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env node | |
const { execSync } = require('child_process'); | |
const { existsSync } = require('fs'); | |
const { resolve, join } = require('path'); | |
const { inspect } = require('util'); | |
/** | |
* Represents a parser for macOS's `mdls` command output, facilitating the | |
* extraction of metadata from files. This class can either directly parse | |
* pre-fetched metadata provided to it, or fetch and parse metadata from a | |
* specified file path using the `mdls` command. It is designed to simplify | |
* interactions with file metadata on macOS by abstracting away the command line | |
* operations and parsing logic into an easy-to-use interface. | |
*/ | |
class MDLSParser { | |
/** | |
* Constructs an instance of MDLSParser, which parses metadata of a file using | |
* macOS's `mdls` command. The constructor either takes a file path and | |
* attempts to resolve and read the file's metadata, or directly takes | |
* pre-fetched metadata. | |
* | |
* @param {Object} options - Configuration options for the parser. | |
* @param {string} [options.filePath] - The path to the file whose metadata is | |
* to be parsed. If provided, the constructor attempts to resolve the path and | |
* fetch metadata using `mdls`. | |
* @param {string} [options.mdlsOutput] - Pre-fetched metadata as a string. If | |
* provided, `filePath` is ignored, and this metadata is used directly. | |
* | |
* @throws {Error} Throws an error if neither `filePath` nor `mdlsOutput` is | |
* provided, or if the file at `filePath` cannot be found. | |
*/ | |
constructor({ filePath, mdlsOutput }) { | |
if (filePath && !mdlsOutput) { | |
let pathToUse = undefined; | |
const tryThese = [ | |
filePath, | |
resolve(filePath), | |
resolve(join('.', filePath)) | |
]; | |
for (let path of tryThese) { | |
if (existsSync(path)) { | |
pathToUse = path; | |
break; | |
} | |
} | |
if (!pathToUse) { | |
console.error('Unable to find the supplied file at any of these places'); | |
tryThese.forEach(console.error); | |
throw new Error('Cannot find path to file to hand to mdls.'); | |
} | |
this.filePath = pathToUse; | |
this.mdlsOutput = execSync(`mdls '${pathToUse.replace(/'/g,"\'")}'`) | |
.toString(); | |
} | |
else if (mdlsOutput) { | |
this.filePath = undefined; | |
this.mdlsOutput = String(mdlsOutput); | |
} | |
else { | |
console.error([ | |
'MDLSParser requires an object with either `filePath` ', | |
'or `mdlsOutut` for input' | |
].join('')); | |
throw new Error('Invalid input to call to new MDLSParser()'); | |
} | |
this.output = this.parse(); | |
} | |
/** | |
* Parses the `mdlsOutput` string to extract metadata into a JSON object. | |
* This method iterates over the `mdlsOutput` string, identifying and | |
* extracting key-value pairs of metadata. The keys are identified by patterns | |
* ending with " = ", and values are parsed based on their format: null, | |
* empty string, array, or other (including numbers and strings). Arrays are | |
* specially handled by extracting the substring enclosed in parentheses and | |
* splitting it into items. This method updates the `output` property of the | |
* instance with the parsed JSON object. | |
* | |
* @returns {Object} The parsed metadata as a JSON object. This object is also | |
* assigned to the `output` property of the instance. | |
*/ | |
parse() { | |
const { extractSubstring } = this.constructor; | |
const { mdlsOutput } = this; | |
let offset = 0; | |
let jsonData = {}; | |
while (offset < mdlsOutput.length) { | |
let keyMatch = mdlsOutput.substring(offset).match(/^(.*?) = /m); | |
if (!keyMatch) break; | |
let key = keyMatch[1]; | |
offset += keyMatch.index + keyMatch[0].length; | |
key = key.trim(); | |
if (mdlsOutput.substring(offset).slice(0,6) === '(null)') { | |
jsonData[key] = null; | |
offset += 6; | |
} | |
else if (mdlsOutput.substring(offset).slice(0,3) === '""\n') { | |
jsonData[key] = ''; | |
} | |
else if (mdlsOutput[offset] === '(') { | |
let { extracted, newOffset } = extractSubstring(mdlsOutput, offset, ['(', ')']); | |
let arrayValue = extracted | |
.slice(1, -1) | |
.split(/\s*,[\n]?\s*/) | |
.map(item => item.trim().replace(/^"|"$/g, '')); | |
jsonData[key] = arrayValue; | |
offset = newOffset; | |
} | |
else { | |
let valueMatch = mdlsOutput.substring(offset).match(/^(.*?)(\n|$)/); | |
if (!valueMatch) break; | |
let value = valueMatch[1].trim().replace(/^"|"$/g, ''); | |
jsonData[key] = isNaN(Number(value)) ? value : Number(value); | |
offset += valueMatch.index + valueMatch[0].length; | |
} | |
} | |
return (this.output = jsonData); | |
} | |
/** | |
* Provides a dynamic view of the `output` property, allowing for controlled | |
* access and interaction with its properties. This getter returns a Proxy | |
* that intercepts and defines custom behavior for fundamental operations | |
* (e.g., property lookup, enumeration, and property check). The Proxy is | |
* particularly useful for adding custom logic to access the properties of | |
* the `output` object. | |
* | |
* The Proxy defines the following traps: | |
* - `get`: Returns the value of the property if it exists. If the property | |
* being accessed is `Symbol.toStringTag`, it returns 'MDLSParserOutput'. | |
* - `has`: Checks if a property exists in the `output` object. | |
* - `ownKeys`: Returns an array of the property names owned by the `output` | |
* object. | |
* | |
* @returns {Proxy} A Proxy wrapping the `output` object, with custom behavior | |
* defined for property access, existence checks, and property enumeration. | |
*/ | |
get get() { | |
const className = this.constructor.name; | |
const targetObj = this.output && typeof this.output === 'object' | |
? this.output | |
: {}; | |
const inspectFn = (target) => (depth = 2, options = { color: true }) => { | |
const data = inspect({ | |
'keyCount': Reflect.ownKeys(target).length | |
}, { ...options, depth: depth - 1 }).replaceAll(/'/g, ''); | |
return `${className}Output ${data}`; | |
}; | |
const proxy = new Proxy(targetObj, { | |
get(target, property, receiver) { | |
if (property === Symbol.toStringTag) { | |
return `${className}Output` | |
} | |
if (property === Symbol.for('receiver')) { | |
return receiver; | |
} | |
if (property === MDLSParser.nodeInspect) { | |
return inspectFn(target); | |
} | |
return target[property]; | |
}, | |
has(target, property) { | |
if ( | |
target === Symbol.toStringTag || | |
target === MDLSParser.nodeInspect | |
) { | |
return true; | |
} | |
return Reflect.ownKeys(target, property); | |
}, | |
ownKeys(target) { | |
return Reflect.ownKeys(target).concat([ | |
Symbol.toStringTag, | |
MDLSParser.nodeInspect | |
]); | |
}, | |
}); | |
const descriptor = { | |
value: inspect(targetObj), | |
enumerable: false, | |
configurable: true, | |
}; | |
for (const obj of [proxy, proxy[Symbol.for('receiver')]]) { | |
Object.defineProperty(obj, MDLSParser.nodeInspect, descriptor); | |
} | |
return proxy; | |
} | |
/** | |
* Retrieves the keys of the `output` object as an array. If the `output` | |
* object is not defined, it returns an empty array. This getter is useful | |
* for quickly accessing the keys of the parsed data stored in the `output` | |
* property without needing to directly interact with the `output` object | |
* itself. It simplifies the process of enumerating the keys present in the | |
* `output`, providing a straightforward way to iterate over or inspect the | |
* data structure's properties. | |
* | |
* @returns {Array<string>} An array of strings representing the keys of the | |
* `output` object. Returns an empty array if `output` is undefined. | |
*/ | |
get keys() { | |
return Object.keys(this?.output ?? {}); | |
} | |
/** | |
* Retrieves the values of the `output` object as an array. If the `output` | |
* object is not defined, it returns an empty array. This getter simplifies | |
* accessing the values of the parsed data stored in the `output` property, | |
* allowing for easy iteration over or inspection of the data structure's | |
* values without direct interaction with the `output` object itself. | |
* | |
* @returns {Array<any>} An array containing the values of the `output` | |
* object. Returns an empty array if `output` is undefined. | |
*/ | |
get values() { | |
return Object.values(this?.output ?? {}); | |
} | |
/** | |
* Retrieves the entries of the `output` object as an array of [key, value] | |
* pairs. If the `output` object is not defined, it returns an empty array. | |
* This getter simplifies accessing both the keys and values of the parsed | |
* data stored in the `output` property, allowing for easy iteration over | |
* or inspection of the data structure's entries without direct interaction | |
* with the `output` object itself. | |
* | |
* @returns {Array<[string, any]>} An array containing the entries of the | |
* `output` object as [key, value] pairs. Returns an empty array if `output` | |
* is undefined. | |
*/ | |
get entries() { | |
return Object.entries(this?.output ?? {}); | |
} | |
/** | |
* Custom iterator generator for the MDLSParser instance. This iterator | |
* allows for easy iteration over the `output` object's entries (key-value | |
* pairs) using the `for...of` loop or other iterable protocols. If the | |
* `output` object is not defined or is not an object, the iterator will | |
* not yield any values, effectively behaving as an iterator over an empty | |
* collection. | |
* | |
* @yields {[string, any]} Yields an array containing a key-value pair | |
* [key, value] from the `output` object for each iteration. | |
*/ | |
*[Symbol.iterator]() { | |
if (!this.output || typeof this.output !== 'object') { | |
return; | |
} | |
for (const entry of Object.entries(this.output)) { | |
yield entry; | |
} | |
} | |
/** | |
* A getter for the default string description of the MDLSParser instance. | |
* This property is used in object-to-string conversions and is accessed | |
* internally by methods like `Object.prototype.toString`. It simplifies | |
* identifying the type of the MDLSParser instance in debugging or logging | |
* scenarios. By default, it returns the name of the constructor, aiding | |
* in quickly identifying the object's type in console outputs or logs. | |
* | |
* @returns {string} The name of the constructor of the instance, which | |
* helps in identifying the object's type. | |
*/ | |
get [Symbol.toStringTag]() { return this.constructor.name; } | |
/** | |
* Custom inspection function for Node.js `util.inspect` that formats the | |
* MDLSParser instance's information. This method is invoked when | |
* `util.inspect` is called on an instance of MDLSParser, providing a | |
* customized string representation of the instance. It includes the length | |
* of the `mdlsOutput` and the number of keys in the `output` object. | |
* | |
* @param {number} depth The depth to which `util.inspect` will recurse. | |
* @param {object} options Formatting options passed to `util.inspect`. | |
* @param {function} inspect Reference to the `util.inspect` function. | |
* @returns {string} A formatted string representing the MDLSParser instance. | |
*/ | |
[Symbol.for('nodejs.util.inspect.custom')](depth, options, inspect) { | |
const data = inspect({ | |
'mdlsOutput (length)': this.mdlsOutput.length, | |
'parsed (keys)': Reflect.ownKeys(this.output).length, | |
}, { ...options, depth: depth - 1 }).replaceAll(/'/g, ''); | |
return `MDLSParser ${data}`; | |
} | |
/** | |
* Extracts a substring from `input` starting at `offset` and enclosed by | |
* `tokens`. This method is designed to handle nested structures and can | |
* ignore escaped tokens within strings. It's particularly useful for parsing | |
* complex data formats or extracting nested information. | |
* | |
* @param {string} input The string from which to extract the substring. | |
* @param {number} offset The position in `input` to start searching for | |
* the substring. | |
* @param {[string, string]} tokens An array containing two elements: the | |
* opening and closing tokens that define the boundaries of the substring. | |
* @returns {{extracted: string|null, newOffset: number}} An object | |
* containing the `extracted` substring (null if not found) and `newOffset`, | |
* the position after the end of the extracted substring. If no valid | |
* substring is found, `newOffset` will be the same as the input `offset`. | |
*/ | |
static extractSubstring(input, offset, tokens) { | |
let [openToken, closeToken] = tokens; | |
let depth = 0; | |
let start = -1; | |
let end = -1; | |
let escapeNext = false; | |
let inString = openToken === '"' && closeToken === '"'; | |
for (let i = offset; i < input.length; i++) { | |
const char = input[i]; | |
if (inString && char === '\\' && !escapeNext) { | |
escapeNext = true; | |
continue; | |
} | |
if (char === openToken && !escapeNext) { | |
depth++; | |
if (start === -1) start = i; | |
} else if (char === closeToken && !escapeNext) { | |
depth--; | |
if (depth === 0) { | |
end = i; | |
break; | |
} | |
} | |
escapeNext = false; | |
} | |
if (start !== -1 && end !== -1) { | |
const extracted = inString | |
? input.slice(start + 1, end) | |
: input.slice(start, end + 1); | |
return { extracted, newOffset: end + 1 }; | |
} else { | |
return { extracted: null, newOffset: offset }; | |
} | |
} | |
/** | |
* Provides a custom inspection symbol for Node.js's `util.inspect` method. | |
* This enables defining custom object representations in the output of | |
* `util.inspect`, useful for debugging or logging. When an object includes | |
* a method keyed by this symbol, it customizes how the object is represented. | |
* | |
* @returns {symbol} Symbol for custom inspection in Node.js. | |
*/ | |
static get nodeInspect() { | |
return Symbol.for('nodejs.util.inspect.custom'); | |
} | |
} | |
const interpreter = process.argv[0]; | |
const script = process.argv[1]; | |
const args = process.argv.slice(2); | |
const filePath = process.argv[2]; | |
if (!args.length) { | |
console.error(`Usage: ${process.argv[1]} <file_path> [<top-level-key-name>] [<index>] [json]\n`); | |
console.error(`\x1b[3mIf "json" is not specified, the output will be raw colorized output`); | |
console.error(`which will be harder to parse and work with. Specify "json" if you are scripting`); | |
console.error(`the output.\x1b[23m`); | |
process.exit(1); | |
} | |
args.hasRemove = function(predicate, valueIfMissing) { | |
let value = this.find(predicate); | |
let index = this.indexOf(value); | |
if (~index) { | |
this.splice(index,1); | |
} | |
return ~index ? value : valueIfMissing; | |
} | |
const mdls = new MDLSParser({ filePath }); | |
const useJSON = args.hasRemove(k => k.toLowerCase() === 'json', false); | |
const useColor = !useJSON; | |
const format = (output) => useColor ? inspect(output, { colors: true }) : JSON.stringify(output, null, 2); | |
if (args.length === 1) { | |
console.log(format(mdls.output)); | |
} | |
else { | |
const index = args.hasRemove(v => !isNaN(Number(v))); | |
const key = args[1]; | |
const value = Array.isArray(mdls.output[key]) && index !== undefined | |
? mdls.output[key][index] | |
: mdls.output[key]; | |
console.log(format(value)); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const { execSync } = require('child_process'); | |
const { existsSync } = require('fs'); | |
const { resolve, join } = require('path'); | |
const { inspect } = require('util'); | |
/** | |
* Represents a parser for macOS's `mdls` command output, facilitating the | |
* extraction of metadata from files. This class can either directly parse | |
* pre-fetched metadata provided to it, or fetch and parse metadata from a | |
* specified file path using the `mdls` command. It is designed to simplify | |
* interactions with file metadata on macOS by abstracting away the command line | |
* operations and parsing logic into an easy-to-use interface. | |
*/ | |
class MDLSParser { | |
/** | |
* Constructs an instance of MDLSParser, which parses metadata of a file using | |
* macOS's `mdls` command. The constructor either takes a file path and | |
* attempts to resolve and read the file's metadata, or directly takes | |
* pre-fetched metadata. | |
* | |
* @param {Object} options - Configuration options for the parser. | |
* @param {string} [options.filePath] - The path to the file whose metadata is | |
* to be parsed. If provided, the constructor attempts to resolve the path and | |
* fetch metadata using `mdls`. | |
* @param {string} [options.mdlsOutput] - Pre-fetched metadata as a string. If | |
* provided, `filePath` is ignored, and this metadata is used directly. | |
* | |
* @throws {Error} Throws an error if neither `filePath` nor `mdlsOutput` is | |
* provided, or if the file at `filePath` cannot be found. | |
*/ | |
constructor({ filePath, mdlsOutput }) { | |
if (filePath && !mdlsOutput) { | |
let pathToUse = undefined; | |
const tryThese = [ | |
filePath, | |
resolve(filePath), | |
resolve(join('.', filePath)) | |
]; | |
for (let path of tryThese) { | |
if (existsSync(path)) { | |
pathToUse = path; | |
break; | |
} | |
} | |
if (!pathToUse) { | |
console.error('Unable to find the supplied file at any of these places'); | |
tryThese.forEach(console.error); | |
throw new Error('Cannot find path to file to hand to mdls.'); | |
} | |
this.filePath = pathToUse; | |
this.mdlsOutput = execSync(`mdls '${pathToUse.replace(/'/g,"\'")}'`) | |
.toString(); | |
} | |
else if (mdlsOutput) { | |
this.filePath = undefined; | |
this.mdlsOutput = String(mdlsOutput); | |
} | |
else { | |
console.error([ | |
'MDLSParser requires an object with either `filePath` ', | |
'or `mdlsOutut` for input' | |
].join('')); | |
throw new Error('Invalid input to call to new MDLSParser()'); | |
} | |
this.output = this.parse(); | |
} | |
/** | |
* Parses the `mdlsOutput` string to extract metadata into a JSON object. | |
* This method iterates over the `mdlsOutput` string, identifying and | |
* extracting key-value pairs of metadata. The keys are identified by patterns | |
* ending with " = ", and values are parsed based on their format: null, | |
* empty string, array, or other (including numbers and strings). Arrays are | |
* specially handled by extracting the substring enclosed in parentheses and | |
* splitting it into items. This method updates the `output` property of the | |
* instance with the parsed JSON object. | |
* | |
* @returns {Object} The parsed metadata as a JSON object. This object is also | |
* assigned to the `output` property of the instance. | |
*/ | |
parse() { | |
const { extractSubstring } = this.constructor; | |
const { mdlsOutput } = this; | |
let offset = 0; | |
let jsonData = {}; | |
while (offset < mdlsOutput.length) { | |
let keyMatch = mdlsOutput.substring(offset).match(/^(.*?) = /m); | |
if (!keyMatch) break; | |
let key = keyMatch[1]; | |
offset += keyMatch.index + keyMatch[0].length; | |
key = key.trim(); | |
if (mdlsOutput.substring(offset).slice(0,6) === '(null)') { | |
jsonData[key] = null; | |
offset += 6; | |
} | |
else if (mdlsOutput.substring(offset).slice(0,3) === '""\n') { | |
jsonData[key] = ''; | |
} | |
else if (mdlsOutput[offset] === '(') { | |
let { extracted, newOffset } = extractSubstring(mdlsOutput, offset, ['(', ')']); | |
let arrayValue = extracted | |
.slice(1, -1) | |
.split(/\s*,[\n]?\s*/) | |
.map(item => item.trim().replace(/^"|"$/g, '')); | |
jsonData[key] = arrayValue; | |
offset = newOffset; | |
} | |
else { | |
let valueMatch = mdlsOutput.substring(offset).match(/^(.*?)(\n|$)/); | |
if (!valueMatch) break; | |
let value = valueMatch[1].trim().replace(/^"|"$/g, ''); | |
jsonData[key] = isNaN(Number(value)) ? value : Number(value); | |
offset += valueMatch.index + valueMatch[0].length; | |
} | |
} | |
return (this.output = jsonData); | |
} | |
/** | |
* Provides a dynamic view of the `output` property, allowing for controlled | |
* access and interaction with its properties. This getter returns a Proxy | |
* that intercepts and defines custom behavior for fundamental operations | |
* (e.g., property lookup, enumeration, and property check). The Proxy is | |
* particularly useful for adding custom logic to access the properties of | |
* the `output` object. | |
* | |
* The Proxy defines the following traps: | |
* - `get`: Returns the value of the property if it exists. If the property | |
* being accessed is `Symbol.toStringTag`, it returns 'MDLSParserOutput'. | |
* - `has`: Checks if a property exists in the `output` object. | |
* - `ownKeys`: Returns an array of the property names owned by the `output` | |
* object. | |
* | |
* @returns {Proxy} A Proxy wrapping the `output` object, with custom behavior | |
* defined for property access, existence checks, and property enumeration. | |
*/ | |
get get() { | |
const className = this.constructor.name; | |
const targetObj = this.output && typeof this.output === 'object' | |
? this.output | |
: {}; | |
const inspectFn = (target) => (depth = 2, options = { color: true }) => { | |
const data = inspect({ | |
'keyCount': Reflect.ownKeys(target).length | |
}, { ...options, depth: depth - 1 }).replaceAll(/'/g, ''); | |
return `${className}Output ${data}`; | |
}; | |
const proxy = new Proxy(targetObj, { | |
get(target, property, receiver) { | |
if (property === Symbol.toStringTag) { | |
return `${className}Output` | |
} | |
if (property === Symbol.for('receiver')) { | |
return receiver; | |
} | |
if (property === MDLSParser.nodeInspect) { | |
return inspectFn(target); | |
} | |
return target[property]; | |
}, | |
has(target, property) { | |
if ( | |
target === Symbol.toStringTag || | |
target === MDLSParser.nodeInspect | |
) { | |
return true; | |
} | |
return Reflect.ownKeys(target, property); | |
}, | |
ownKeys(target) { | |
return Reflect.ownKeys(target).concat([ | |
Symbol.toStringTag, | |
MDLSParser.nodeInspect | |
]); | |
}, | |
}); | |
const descriptor = { | |
value: inspect(targetObj), | |
enumerable: false, | |
configurable: true, | |
}; | |
for (const obj of [proxy, proxy[Symbol.for('receiver')]]) { | |
Object.defineProperty(obj, MDLSParser.nodeInspect, descriptor); | |
} | |
return proxy; | |
} | |
/** | |
* Retrieves the keys of the `output` object as an array. If the `output` | |
* object is not defined, it returns an empty array. This getter is useful | |
* for quickly accessing the keys of the parsed data stored in the `output` | |
* property without needing to directly interact with the `output` object | |
* itself. It simplifies the process of enumerating the keys present in the | |
* `output`, providing a straightforward way to iterate over or inspect the | |
* data structure's properties. | |
* | |
* @returns {Array<string>} An array of strings representing the keys of the | |
* `output` object. Returns an empty array if `output` is undefined. | |
*/ | |
get keys() { | |
return Object.keys(this?.output ?? {}); | |
} | |
/** | |
* Retrieves the values of the `output` object as an array. If the `output` | |
* object is not defined, it returns an empty array. This getter simplifies | |
* accessing the values of the parsed data stored in the `output` property, | |
* allowing for easy iteration over or inspection of the data structure's | |
* values without direct interaction with the `output` object itself. | |
* | |
* @returns {Array<any>} An array containing the values of the `output` | |
* object. Returns an empty array if `output` is undefined. | |
*/ | |
get values() { | |
return Object.values(this?.output ?? {}); | |
} | |
/** | |
* Retrieves the entries of the `output` object as an array of [key, value] | |
* pairs. If the `output` object is not defined, it returns an empty array. | |
* This getter simplifies accessing both the keys and values of the parsed | |
* data stored in the `output` property, allowing for easy iteration over | |
* or inspection of the data structure's entries without direct interaction | |
* with the `output` object itself. | |
* | |
* @returns {Array<[string, any]>} An array containing the entries of the | |
* `output` object as [key, value] pairs. Returns an empty array if `output` | |
* is undefined. | |
*/ | |
get entries() { | |
return Object.entries(this?.output ?? {}); | |
} | |
/** | |
* Custom iterator generator for the MDLSParser instance. This iterator | |
* allows for easy iteration over the `output` object's entries (key-value | |
* pairs) using the `for...of` loop or other iterable protocols. If the | |
* `output` object is not defined or is not an object, the iterator will | |
* not yield any values, effectively behaving as an iterator over an empty | |
* collection. | |
* | |
* @yields {[string, any]} Yields an array containing a key-value pair | |
* [key, value] from the `output` object for each iteration. | |
*/ | |
*[Symbol.iterator]() { | |
if (!this.output || typeof this.output !== 'object') { | |
return; | |
} | |
for (const entry of Object.entries(this.output)) { | |
yield entry; | |
} | |
} | |
/** | |
* A getter for the default string description of the MDLSParser instance. | |
* This property is used in object-to-string conversions and is accessed | |
* internally by methods like `Object.prototype.toString`. It simplifies | |
* identifying the type of the MDLSParser instance in debugging or logging | |
* scenarios. By default, it returns the name of the constructor, aiding | |
* in quickly identifying the object's type in console outputs or logs. | |
* | |
* @returns {string} The name of the constructor of the instance, which | |
* helps in identifying the object's type. | |
*/ | |
get [Symbol.toStringTag]() { return this.constructor.name; } | |
/** | |
* Custom inspection function for Node.js `util.inspect` that formats the | |
* MDLSParser instance's information. This method is invoked when | |
* `util.inspect` is called on an instance of MDLSParser, providing a | |
* customized string representation of the instance. It includes the length | |
* of the `mdlsOutput` and the number of keys in the `output` object. | |
* | |
* @param {number} depth The depth to which `util.inspect` will recurse. | |
* @param {object} options Formatting options passed to `util.inspect`. | |
* @param {function} inspect Reference to the `util.inspect` function. | |
* @returns {string} A formatted string representing the MDLSParser instance. | |
*/ | |
[Symbol.for('nodejs.util.inspect.custom')](depth, options, inspect) { | |
const data = inspect({ | |
'mdlsOutput (length)': this.mdlsOutput.length, | |
'parsed (keys)': Reflect.ownKeys(this.output).length, | |
}, { ...options, depth: depth - 1 }).replaceAll(/'/g, ''); | |
return `MDLSParser ${data}`; | |
} | |
/** | |
* Extracts a substring from `input` starting at `offset` and enclosed by | |
* `tokens`. This method is designed to handle nested structures and can | |
* ignore escaped tokens within strings. It's particularly useful for parsing | |
* complex data formats or extracting nested information. | |
* | |
* @param {string} input The string from which to extract the substring. | |
* @param {number} offset The position in `input` to start searching for | |
* the substring. | |
* @param {[string, string]} tokens An array containing two elements: the | |
* opening and closing tokens that define the boundaries of the substring. | |
* @returns {{extracted: string|null, newOffset: number}} An object | |
* containing the `extracted` substring (null if not found) and `newOffset`, | |
* the position after the end of the extracted substring. If no valid | |
* substring is found, `newOffset` will be the same as the input `offset`. | |
*/ | |
static extractSubstring(input, offset, tokens) { | |
let [openToken, closeToken] = tokens; | |
let depth = 0; | |
let start = -1; | |
let end = -1; | |
let escapeNext = false; | |
let inString = openToken === '"' && closeToken === '"'; | |
for (let i = offset; i < input.length; i++) { | |
const char = input[i]; | |
if (inString && char === '\\' && !escapeNext) { | |
escapeNext = true; | |
continue; | |
} | |
if (char === openToken && !escapeNext) { | |
depth++; | |
if (start === -1) start = i; | |
} else if (char === closeToken && !escapeNext) { | |
depth--; | |
if (depth === 0) { | |
end = i; | |
break; | |
} | |
} | |
escapeNext = false; | |
} | |
if (start !== -1 && end !== -1) { | |
const extracted = inString | |
? input.slice(start + 1, end) | |
: input.slice(start, end + 1); | |
return { extracted, newOffset: end + 1 }; | |
} else { | |
return { extracted: null, newOffset: offset }; | |
} | |
} | |
/** | |
* Provides a custom inspection symbol for Node.js's `util.inspect` method. | |
* This enables defining custom object representations in the output of | |
* `util.inspect`, useful for debugging or logging. When an object includes | |
* a method keyed by this symbol, it customizes how the object is represented. | |
* | |
* @returns {symbol} Symbol for custom inspection in Node.js. | |
*/ | |
static get nodeInspect() { | |
return Symbol.for('nodejs.util.inspect.custom'); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment