Skip to content

Instantly share code, notes, and snippets.

@timepp
Created October 22, 2024 04:12
Show Gist options
  • Save timepp/605f9d15818dad3242e2bbd703676b1c to your computer and use it in GitHub Desktop.
Save timepp/605f9d15818dad3242e2bbd703676b1c to your computer and use it in GitHub Desktop.
A pattern to handle command line arguments + config file uniformly, in a type-safe way
import { parseArgs } from "jsr:@std/cli/parse-args"
// Define the option names, their default values, and (implicitly) their types, using a DRY (dont repeat yourself) way
// The option name itself will be used as the command line switch, and the keys in config json file
// This object is also used to enumerate all options
// Supported type: number, string, boolean, string array
const defaultConfig = {
area: 'Recording',
steps: ['ocv', 'classify', 'excel'] as 'ocv'|'classify'|'excel'[],
startDate: '',
endDate: '',
excel: '',
classifyBatchCount: 5,
browser: 'msedge',
browserProfile: ''
}
// Define the option descriptions and command line aliases
const configDescription: {[P in keyof typeof defaultConfig]:[/*doc*/string, /*alias*/string?]} = {
area: [`area name, one of ${ac.areaConf.map(v => v.name).join(',')}`],
steps: ['steps to execute'],
startDate: ['start date, e.g. 2024-10-22'],
endDate: ['end date, if omitted, only one day will be processed'],
excel: ['output excel file'],
classifyBatchCount: ['number of classify batch', 'n'],
browser: ['browser to use: either msedge or chrome'],
browserProfile: ['browser profile to use, if not specified, default profile will be used']
}
// Derive config from the default config object
type RunConfig = typeof defaultConfig
function showHelp() {
console.log('Usage: deno run -A run.ts <OPTIONS>')
console.log()
console.log('Options:')
for (const k in configDescription) {
const kk = k as keyof typeof configDescription
const v = configDescription[kk] as [string, string]
const flag = v[1] ? ` (${v[1]})` : ''
const defaultValue = JSON.stringify(defaultConfig[kk])
console.log(` - %c${kk}%c${flag} %c= ${defaultValue}:`, 'font-weight: bold; color: blue', 'color: blue', 'color: grey', v[0])
}
console.log()
console.log('Steps: ocv, classify, excel')
console.log(' ocv: get ocv data')
console.log(' classify: classify ocv data using GPT')
console.log(' excel: update excel data')
console.log('if steps is not specified, all steps will be executed, otherwise only the specified steps will be executed.')
console.log()
console.log(' You can put the same options in run.json, so that you don\'t need to specify them in command line.')
}
function getConfigFromFile(path: string) {
try {
return JSON.parse(Deno.readTextFileSync(path)) as RunConfig
} catch (_e) {
return {} as RunConfig
}
}
async function main() {
const commandLineArgs = parseArgs(Deno.args, {
collect: Object.keys(defaultConfig).filter(v => Array.isArray(defaultConfig[v as keyof typeof defaultConfig])),
alias: Object.fromEntries(Object.entries(configDescription).map(v => [v[1][1], v[0]])) as { [k: string]: string },
}) as unknown as typeof defaultConfig & { _: string[], help?: boolean }
// Handle --help
if (commandLineArgs.help) {
showHelp()
return
}
// Merge the default config, config file, commandline args, with the expected priority
const conf = {...defaultConfig, ...getConfigFromFile('run.json'), ...commandLineArgs} as RunConfig
// Use config in the remaining logic.
...
}
@timepp
Copy link
Author

timepp commented Oct 22, 2024

This is the effect of "--help" for the code:

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment