import { ArgParser, ParsingResult, ParseContext, Register } from './argparser.ts'; import { findOption } from './newparser/findOption.ts'; import { ProvidesHelp, Descriptive, ShortDoc, LongDoc, EnvDoc, } from './helpdoc.ts'; import { Type, extendType, OutputOf, HasType } from './type.ts'; import chalk from 'https://deno.land/std@0.85.0/fmt/colors.ts'; import { Default } from './default.ts'; import { AllOrNothing } from './utils.ts'; import * as Result from './Result.ts'; import { boolean as booleanIdentity } from './types.ts'; import { getEnvVar } from './getEnvVar.ts'; type FlagConfig> = LongDoc & HasType & Partial & AllOrNothing>>; /** * A decoder from `string` to `boolean` * works for `true` and `false` only. */ export const boolean: Type = { async from(str) { if (str === 'true') return true; if (str === 'false') return false; throw new Error( `expected value to be either "true" or "false". got: "${str}"` ); }, displayName: 'true/false', defaultValue: () => false, }; export function fullFlag>( config: FlagConfig ): ArgParser> & ProvidesHelp & Register & Partial { const decoder = extendType(boolean, config.type); return { description: config.description ?? config.type.description, helpTopics() { let usage = `--${config.long}`; if (config.short) { usage += `, -${config.short}`; } const defaults: string[] = []; if (config.env) { const envVar = getEnvVar(config.env); const env = envVar === undefined ? '' : `=${chalk.italic(envVar)}`; defaults.push(`env: ${config.env}${env}`); } try { const defaultValueFn = config.defaultValue ?? config.type.defaultValue; const defaultValueIsSerializable = config.defaultValueIsSerializable ?? config.type.defaultValueIsSerializable; if (defaultValueFn && defaultValueIsSerializable) { const defaultValue = defaultValueFn(); defaults.push('default: ' + chalk.italic(defaultValue)); } } catch (e) {} return [ { category: 'flags', usage, defaults, description: config.description ?? config.type.description ?? 'self explanatory', }, ]; }, register(opts) { opts.forceFlagLongNames.add(config.long); if (config.short) { opts.forceFlagShortNames.add(config.short); } }, async parse({ nodes, visitedNodes, }: ParseContext): Promise>> { const options = findOption(nodes, { longNames: [config.long], shortNames: config.short ? [config.short] : [], }).filter((x) => !visitedNodes.has(x)); options.forEach((opt) => visitedNodes.add(opt)); if (options.length > 1) { return Result.err({ errors: [ { nodes: options, message: 'Expected 1 occurence, got ' + options.length, }, ], }); } const valueFromEnv = config.env ? getEnvVar(config.env) : undefined; let rawValue: string; let envPrefix = ''; if (options.length === 0 && valueFromEnv !== undefined) { rawValue = valueFromEnv; envPrefix = `env[${chalk.italic(config.env)}]: `; } else if ( options.length === 0 && typeof config.type.defaultValue === 'function' ) { try { return Result.ok(config.type.defaultValue()); } catch (e) { const message = `Default value not found for '--${config.long}': ${e.message}`; return Result.err({ errors: [{ message, nodes: [] }], }); } } else if (options.length === 1) { rawValue = options[0].value?.node.raw ?? 'true'; } else { return Result.err({ errors: [ { nodes: [], message: `No value provided for --${config.long}` }, ], }); } const decoded = await Result.safeAsync(decoder.from(rawValue)); if (Result.isErr(decoded)) { return Result.err({ errors: [ { nodes: options, message: envPrefix + decoded.error.message, }, ], }); } return decoded; }, }; } type BooleanType = Type; /** * Decodes an argument which is in the form of a key and a boolean value, and allows parsing the following ways: * * - `--long` where `long` is the provided `long` * - `-s=value` where `s` is the provided `short` * Shorthand forms can be combined: * - `-abcd` will call all flags for the short forms of `a`, `b`, `c` and `d`. * @param config flag configurations */ export function flag>( config: FlagConfig ): ArgParser> & ProvidesHelp & Register & Partial; export function flag( config: LongDoc & Partial & ShortDoc & Descriptive & EnvDoc> & AllOrNothing>> ): ArgParser> & ProvidesHelp & Register & Partial; export function flag( config: LongDoc & Partial & ShortDoc & Descriptive & EnvDoc> & AllOrNothing>> ): ArgParser> & ProvidesHelp & Register & Partial { return fullFlag({ type: booleanIdentity, ...config, }); }