diff --git a/src/interfaces/parser.ts b/src/interfaces/parser.ts index 9fff88b3d..d46616d7c 100644 --- a/src/interfaces/parser.ts +++ b/src/interfaces/parser.ts @@ -221,6 +221,11 @@ export type OptionFlagProps = FlagProps & { * separate on spaces. */ delimiter?: ',' + /** + * Allow input value to be read from stdin. + * Should only be used on one flag at a time. + */ + allowStdin?: boolean } export type FlagParserContext = Command & {token: FlagToken} diff --git a/src/parser/parse.ts b/src/parser/parse.ts index ba273ea8c..392a1cca2 100644 --- a/src/parser/parse.ts +++ b/src/parser/parse.ts @@ -35,7 +35,7 @@ try { } } -const readStdin = async (): Promise => { +export const readStdin = async (): Promise => { const {stdin, stdout} = process // process.stdin.isTTY is true whenever it's running in a terminal. @@ -122,7 +122,7 @@ export class Parser< public async parse(): Promise> { this._debugInput() - const parseFlag = (arg: string): boolean => { + const parseFlag = async (arg: string): Promise => { const {isLong, name} = this.findFlag(arg) if (!name) { const i = arg.indexOf('=') @@ -130,7 +130,7 @@ export class Parser< const sliced = arg.slice(i + 1) this.argv.unshift(sliced) - const equalsParsed = parseFlag(arg.slice(0, i)) + const equalsParsed = await parseFlag(arg.slice(0, i)) if (!equalsParsed) { this.argv.shift() } @@ -149,12 +149,19 @@ export class Parser< } this.currentFlag = flag - const input = isLong || arg.length < 3 ? this.argv.shift() : arg.slice(arg[2] === '=' ? 3 : 2) + let input = isLong || arg.length < 3 ? this.argv.shift() : arg.slice(arg[2] === '=' ? 3 : 2) // if the value ends up being one of the command's flags, the user didn't provide an input if (typeof input !== 'string' || this.findFlag(input).name) { throw new CLIError(`Flag --${name} expects a value`) } + if (flag.allowStdin && input === '-') { + const stdin = await readStdin() + if (stdin) { + input = stdin.trim() + } + } + this.raw.push({flag: flag.name, input, type: 'flag'}) } else { this.raw.push({flag: flag.name, input: arg, type: 'flag'}) @@ -181,7 +188,7 @@ export class Parser< continue } - if (parseFlag(input)) { + if (await parseFlag(input)) { continue } diff --git a/test/parser/parse.test.ts b/test/parser/parse.test.ts index 21edd0ad8..fb4d83afb 100644 --- a/test/parser/parse.test.ts +++ b/test/parser/parse.test.ts @@ -1,12 +1,13 @@ import {assert, config, expect} from 'chai' import * as fs from 'node:fs' import {URL} from 'node:url' -import {SinonStub, createSandbox} from 'sinon' +import {SinonSandbox, SinonStub, createSandbox} from 'sinon' import {Args, Flags} from '../../src' import {CLIError} from '../../src/errors' import {FlagDefault} from '../../src/interfaces/parser' import {parse} from '../../src/parser' +import * as parser from '../../src/parser/parse' config.truncateThreshold = 0 const stripAnsi = require('strip-ansi') @@ -1855,3 +1856,31 @@ See more help with --help`) }) }) }) + +describe('allowStdin', () => { + let sandbox: SinonSandbox + const stdinValue = 'x' + const stdinPromise = new Promise((resolve) => { + resolve(stdinValue) + }) + + beforeEach(() => { + sandbox = createSandbox() + }) + + afterEach(() => { + sandbox.restore() + }) + + it('should read stdin as input for flag', async () => { + sandbox.stub(parser, 'readStdin').returns(stdinPromise) + const out = await parse(['--myflag', '-'], { + flags: { + myflag: Flags.string({allowStdin: true}), + }, + }) + + expect(out.flags.myflag).to.equals(stdinValue) + expect(out.raw[0].input).to.equal('x') + }) +})