Skip to content
This repository has been archived by the owner on Aug 22, 2023. It is now read-only.

feat: allow multiple for arg #63

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

mkg20001
Copy link

@mkg20001 mkg20001 commented Oct 8, 2019

This enables the multiple flag for an argument

The advantage over #61 is that this allows "multiple" arguments right in the middle, although both proposals could co-exist aswell

NOTE: Only one multiple argument is allowed, otherwise they will eat each other's arguments

The strategy to parse it works by checking how many non-multiple arguments should follow

@salesforce-cla
Copy link

salesforce-cla bot commented Oct 8, 2019

Thanks for the contribution! Before we can merge this, we need @mkg20001 to sign the Salesforce.com Contributor License Agreement.

@mkg20001
Copy link
Author

mkg20001 commented Oct 8, 2019

NOTE: I have no clue about typescript. I'd appreciate some guiadance

@codecov
Copy link

codecov bot commented Oct 9, 2019

Codecov Report

Merging #63 into master will decrease coverage by 84.45%.
The diff coverage is n/a.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #63       +/-   ##
==========================================
- Coverage   84.45%   0.00%   -84.46%     
==========================================
  Files          11       1       -10     
  Lines         328      29      -299     
  Branches       91      14       -77     
==========================================
- Hits          277       0      -277     
+ Misses         36      29        -7     
+ Partials       15       0       -15     
Impacted Files Coverage Δ
src/validate.ts 0.00% <0.00%> (-31.43%) ⬇️
src/errors.ts
src/index.ts
src/list.ts
src/deps.ts
src/util.ts
src/flags.ts
src/help.ts
src/screen.ts

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 73c0c4e...d8f27c3. Read the comment docs.

@frangio
Copy link
Contributor

frangio commented Nov 26, 2019

@RasPhilCo Any chance to get this merged?

@RasPhilCo
Copy link
Contributor

RasPhilCo commented Nov 27, 2019

I think we should fix this by fixing how parse handles multiple args with static = false set. It solves this without adding api. See oclif/oclif#234 (comment)

@mkg20001
Copy link
Author

mkg20001 commented Nov 27, 2019

I think we should fix this by fixing how parse handles multiple args with static = false set. It solves this without adding api. See oclif/oclif#234 (comment)

The advantage over #61 is that this allows "multiple" arguments right in the middle, although both proposals could co-exist aswell

@RasPhilCo This extra API would be extremly convinient for some usecases.

Like parsing "<src <src...>> <dst>" (like cp does it), automatically

@RasPhilCo
Copy link
Contributor

On further thought, I'm on board with this. I'll make a few typescript comments. It'll need tests.

src/args.ts Outdated
@@ -22,11 +22,13 @@ export interface ArgBase<T> {

export type RequiredArg<T> = ArgBase<T> & {
required: true
multiple: boolean
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

multiple?: false

src/args.ts Outdated
value: T
}

export type OptionalArg<T> = ArgBase<T> & {
required: false
multiple: boolean
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

multiple?: false

@tiagonapoli
Copy link

I'm in need of this feature too, @mkg20001 if you allow me I can try to contribute on this PR, at least trying to make some tests

@mkg20001
Copy link
Author

@tiagonapoli You've got push access now: https://github.com/mkg20001/parser/invitations

@G-Rath
Copy link
Contributor

G-Rath commented Feb 13, 2020

@mkg20001 if you could rebase this, I'm happy to help write tests :)

@mkg20001
Copy link
Author

@G-Rath I've added you as a collaborator: https://github.com/mkg20001/parser/invitations

@G-Rath
Copy link
Contributor

G-Rath commented Feb 13, 2020

@mkg20001 I've had a play with this, but it doesn't seem to work anymore?

Am I missing something:

export default class MyCommand extends Command {
  public static args: Parser.args.IArg[] = [
    {
      name: 'targetPath',
      required: true,
      // @ts-ignore
      multiple: true
    }
  ];

  public async run(): Promise<void> {
    console.log(this.parse(MyCommand));
  }
}
g-rath:terraport$ nrun cli mycommand 1 2 3
> nrun cli
> node -r tsconfig-paths/register bin/terraport "mycommand" "1" "2" "3"
 ›   Error: Unexpected arguments: 2, 3
 ›   See more help with --help

@mkg20001
Copy link
Author

Hrm. I changed multiple: Boolean to multiple?: false but I don't think that's what broke it.

@G-Rath
Copy link
Contributor

G-Rath commented Feb 13, 2020

I changed multiple: Boolean to multiple?: false but I don't think that's what broke it.

Correct, that won't have changed the behaviour. Types exist at compile only - they never affect the emitted js (that's one of the non-goals of TS) :)

@G-Rath
Copy link
Contributor

G-Rath commented Feb 13, 2020

@mkg20001 ah it's because now the strict property has to be set to false. Without it, the parser won't parse the rest of the arguments.

I feel like this makes strict a bit redundant - it probably should be calculated based off args:

  static get strict() {
    return !this.args.some(arg => arg.multiple);
  }

@G-Rath
Copy link
Contributor

G-Rath commented Feb 13, 2020

@mkg20001 This doesn't seem to be working as expected still: it's not properly handling args after the multiple:

export default class MyCommand extends Command {
  public static args: Parser.args.IArg[] = [
    {
      name: 'targetPath',
      required: true
      // @ts-ignore
      multiple: true
    },
    {
      name: 'again',
      required: true,
      // @ts-ignore
      // multiple: true
    }
  ];

  static strict = false;

  public async run(): Promise<void> {
    console.log(this.parse(MyCommand));
  }
}
g-rath:terraport$ nrun cli mycommand 1 2 3
> nrun cli
> node -r tsconfig-paths/register bin/terraport "mycommand" "1" "2" "3"
{
  args: { targetPath: '1', again: [] },
  argv: [ '1', '2', '3' ],
  flags: {},
  raw: [
    { type: 'arg', input: '1' },
    { type: 'arg', input: '2' },
    { type: 'arg', input: '3' }
  ],
  metadata: { flags: {} }
}

I just want to check if this is the intended behaviour?

@G-Rath
Copy link
Contributor

G-Rath commented Feb 13, 2020

Looking further into this, I think the answer is what @RasPhilCo originally said:

I think we should fix this by fixing how parse handles multiple args with static = false set. It solves this without adding api. See oclif/oclif#234 (comment)

Effectively by having strict as false, the last argument should act as a sponge, socking up all trailing args.

This should provide a simple but powerful api for supporting variadic arguments:

Commands that take at 1 or more args:

export default class FormatFiles extends Command {
  public static args: Parser.args.IArg[] = [
    { name: 'file', required: true },
    { name: 'additionalFiles', required: false }
  ];

  static strict = false;

  public async run(): Promise<void> {
    const {
      args: { file, additionalFiles }
    } = this.parse(FormatFiles);

    const allFiles: string[] = [file].concat(additionalFiles);
  }
}

Commands that take multiple arguments before a final argument (a la cp):

export default class CopyFiles extends Command {
  public static args: Parser.args.IArg[] = [{ name: 'everything' }];

  static strict = false;

  public async run(): Promise<void> {
    const {
      args: { everything }
    } = this.parse(CopyFiles);

    const dest: string = everything.pop();
    const srcs: string[] = everything;
  }
}

I believe the complexity to implement the proposed API above is much smaller than what it would take to implement this PR.

@alexkli
Copy link

alexkli commented May 12, 2020

Any chance of getting this implemented? Any workaround?

Support for array-like arguments (mycli arg ...) seems like a very basic feature. Using flags using multiple: true is not really the same, as mycli -a one -b two is a very different command line UX as cli one two ..., at least to me 😄 .

@nwalters512
Copy link

Hey maintainers, can I do anything to help push this forward?

@mkg20001
Copy link
Author

I'm no longer interested in the development of this PR. Feel free to take my changes and implement them in another PR

@msabramo
Copy link

I am interested in this feature and would be willing to put in some time to get it polished, as long as the maintainers can say that they're willing to merge it.

I think this is a very handy feature for when one wants to have a command like process-image --filter=blur *.jpg or such.

I also think it is very consistent with oclif's current design, because flags already have a multiple property -- https://oclif.io/docs/flags

@msabramo
Copy link

msabramo commented Aug 19, 2021

I found a workaround, in case anyone else finds this useful:

    static args = [{ name: 'path' }];
    static strict = false  // Allow variable length arguments (https://oclif.io/docs/args)
    ...
    getAllPathArgsArray() {
        const { args } = this.parse(TestCommand)
        const indexOfFirstPath = this.argv.findIndex(arg => arg === args.path)
        return this.argv.slice(indexOfFirstPath)
    }

or perhaps if you're already calling this.parse elsewhere to process other arguments and flags, which likely you are, then maybe this is better:

    const oclifParseResult = this.parse(TestCommand)
    const { flags, args } = oclifParseResult
    const allPathArgsArray = this.getAllPathArgsArray(oclifParseResult)

    getAllPathArgsArray(oclifParseResult) {
        const indexOfFirstPath = this.argv.findIndex(arg => arg === oclifParseResult.args.path)
        return this.argv.slice(indexOfFirstPath)
    }

I do find it surprising that oclif doesn't just support a multiple property for args, as it does for flags.

@NickTomlin
Copy link

Inspired by @msabramo I was able to work around this by using the raw property of ParseResult.

$ mycommand --cluster biz example/simple/simple.workflow.json bar biz

Results in a parse result like this:

{
  args: { path: 'example/simple/simple.workflow.json' },
  argv: [ 'example/simple/simple.workflow.json', 'bar', 'biz' ],
  flags: { cluster: 'biz' },
  raw: [
    { type: 'flag', flag: 'cluster', input: 'biz' },
    { type: 'arg', input: 'example/simple/simple.workflow.json' },
    { type: 'arg', input: 'bar' },
    { type: 'arg', input: 'biz' }
  ],
  metadata: { flags: {} }
}

Which I then filtered the raw elements like so:

class MyCommand {

  static flags = { /* your flags */ }
  static args = [{ name: 'path', multiple: true}];
  // allow for passing multiple non typed args through (https://oclif.io/docs/args)
  static strict = false
  run () {
    const parseResult = this.parse(MyCommand);
    const paths = parseResult.raw
      .filter(arg => arg.type == 'arg')
      .map(arg =>  arg.input)
  }
}

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants