Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Determine if default was used? #513

Closed
FallingSnow opened this issue May 23, 2016 · 19 comments · Fixed by TypeScriptToLua/TypeScriptToLua#165
Closed

Determine if default was used? #513

FallingSnow opened this issue May 23, 2016 · 19 comments · Fixed by TypeScriptToLua/TypeScriptToLua#165

Comments

@FallingSnow
Copy link

Is there a way to determine if a yargs.argv argument was set explicitly by the user rather than by the default?

@maxrimue
Copy link
Member

There's no official method provided by yargs, but instead you could keep the original process.argv array and perform a check on it, if necessary, if it holds a certain property, which would mean the user explicitly passed it:

Boolean(process.argv.indexOf("yourOption") > -1) // Returns true if "yourOption" was passed

Does that solve your problem?

@FallingSnow
Copy link
Author

This is the exact same fix I already applied.

It's unfortunate that yargs doesn't have a feature like this. Thanks for your help.

@nexdrew
Copy link
Member

nexdrew commented May 24, 2016

@FallingSnow There's also another workaround that makes it slightly easier to support aliases (and other parsing translations): you can use a fake default value that you can check for and swap out after parsing. Here's an example using a custom .check() function:

var yargs = require('yargs')
  .option('foo', {
    default: '__some_unlikely_value_as_fake_default__'
  })
  .check(function (argv) {
    if (argv.foo === yargs.getOptions().default.foo) {
      console.log('foo not specified by user')
      argv.foo = 'real_default'
    }
    return true
  })
console.log(yargs.argv.foo)

Besides the workarounds, what kind of support/API for this would you like to see in yargs? Something like yargs.parsed.defaultsUsed which maybe looks like { foo: 'default value' }?

@FallingSnow
Copy link
Author

Thanks for the work around.

I would like to see a simple boolean that identifies if yargs used the default. For example yargs.parsed.foo.defaultUsed. You could also use it to store extra information like whether the alias was used or the count.

yargs = {
  parsed: {
    foo: {
      defaultUsed: true,
      aliasUsed: false,
      count: 1
    }
  }
}

But something like a yargs.parsed.defaultsUsed collection would work too, although I think the value should be a boolean, not the default value.

@maxrimue
Copy link
Member

I'm not entirely certain if we should add this to the API, because of the following reasons:

  1. I don't really see a use case here that applies to a lot of users. I don't want to question whether you need this feature or not, but I doubt that enough people out there do.
  2. What you could achieve using this new API can also be achieved using just a single line of code which I showed you earlier. Since the original process.argv array is there anyway and isn't so big, you also don't need too much resources to perform that code.

Any thoughts?

@FallingSnow
Copy link
Author

  1. You're probably right. If no one else has brought this up as a problem yet, it's most likely an edge case.
  2. Unfortunately the one line didn't cut it for me. I ended up using this
function userSetOption(option) {
    let formattedOption;
    if (option.length > 1) {
        formattedOption = '--' + option;
    }
    else {
        formattedOption = '-' + option;
    }

    if (process.argv.indexOf(option) > -1)
        return true;

    // Handle aliases for same option
    for (let aliasIndex in yargs.choices().parsed.aliases[option]) {
        let alias = yargs.choices().parsed.aliases[option][aliasIndex];
        let formattedAlias;
        if (alias.length > 1) {
            formattedAlias = '--' + alias;
        }
        else {
            formattedAlias = '-' + alias;
        }

        if (process.argv.indexOf(formattedAlias) > -1) {
            return true;
        }
    }

    return false;
}

@maxrimue
Copy link
Member

@FallingSnow Ah yeah I didn't notice you have to check for - and aliases, too. That of course makes it a bit more complicated. I think you can shrink down your code a little though, like this:

function userSetOption(option) {
    function searchForOption(option) {
      (process.argv.indexOf(option) > -1) && return true;
      return false
    }

    if (searchForOption(`-${option}`) || searchForOption(`--${option}`))
      return true;

    // Handle aliases for same option
    for (let aliasIndex in yargs.choices().parsed.aliases[option]) {
      let alias = yargs.choices().parsed.aliases[option][aliasIndex];
      if (searchForOption(`-${alias}`) || searchForOption(`--${alias}`))
        return true;
    }

    return false;
}

@FallingSnow
Copy link
Author

Ah, very nifty, thx.

@pedro93
Copy link

pedro93 commented Feb 8, 2017

Hi, I was also looking for this option.

My use case is to check if a user explicitly wants a certain option value. Lets say I have a set of options with default values (a, b, c) and a config file where I can also defined these values:

{
 "a": value1,
 "b": value2,
 "c": true
}

I want to "merge" the options from both sources with the following priority:

  • Explicit cmd line property value
  • Explicit config file property value
  • Implicit yargs's option default value

Imagine a user has a config file they can't or don't want to modify and instead just want to explicitly override an option for a quick test.

I know you can workaround this but it would be helpful (only if it's easy and minimal to implement) to have yargs provide this functionality internally.

Thanks for reading this and awesome library 👍

@plroebuck
Copy link

Additional discussion in the yargs-parser concerning determine if a yargs.argv argument was set explicitly by the user.

@brewster1134
Copy link

👍 +1 for this feature as well...
the use case i need it for is, i need support for checking environment variables. i want to still define a default value, but i don't want to bother checking for the env var if the user passed a specific value via the cli.
i also need to check a config.yaml file, but the config file has more than just possible values for command options, so i cannot use the yargs .config api method.

@brewster1134
Copy link

brewster1134 commented Jan 9, 2019

I would like to see a simple boolean that identifies if yargs used the default. For example yargs.parsed.foo.defaultUsed. You could also use it to store extra information like whether the alias was used or the count.

yargs = {
  parsed: {
    foo: {
      defaultUsed: true,
      aliasUsed: false,
      count: 1
    }
  }
}

But something like a yargs.parsed.defaultsUsed collection would work too, although I think the value should be a boolean, not the default value.

i also like the idea of the args object having meta data associated with each option. it would also allow passing a cleaner args object... so instead of a key for both the option and the alias, you could re-map it to exclude the raw process.argv, and easily exclude the aliases, while still supporting them

and not only for the command callbacks, but the coerce callbacks as well could include the command that was used for globally defined options that apply to all commands, as referenced here #1266 :)

@Crono1981
Copy link

I really would benefit from such a feature myself. In my scenario, I have an output file argument which has a default value. I also have a flag option. If it's off, the app output goes straight to the console but if it's on then it goes into the specified file.

This way, the following will cause output to go to the console:

cli

... the following will output to the default file location:

cli -toFile

... and the following will output to a non-default file:

cli -toFile -o myfile.txt

What I wish for is being able to "forgive" my user for the following input:

cli -o myfile.txt

Having the user specifying a custom file should automatically imply the -toFile option.

@robinmoisson
Copy link

robinmoisson commented Feb 9, 2022

Updating @maxrimue 's code in this comment, the choices() method requires a parameter or an assertion will fail and throw an error (see the relevant code here):

YError: Not enough arguments provided. Expected 1 but received 0.

It seems it can just be omitted:

function userSetOption(option) {
    function searchForOption(option) {
      return process.argv.indexOf(option) > -1;
    }

    if (searchForOption(`-${option}`) || searchForOption(`--${option}`)) {
      return true;
    }

    // Handle aliases for same option
    for (let aliasIndex in yargs.parsed.aliases[option]) {
      let alias = yargs.parsed.aliases[option][aliasIndex];
      if (searchForOption(`-${alias}`) || searchForOption(`--${alias}`))
        return true;
    }

    return false;
}

@marikaner
Copy link

A bit late to the party, I guess, but I would like to have this feature as well.
My use case is, that I want to show a deprecation note for deprecated options (which probably is another feature request).
I would like to know whether a deprecated option was set by a user or by default.
Sure, I can find out if it was used through process.argv, however, I also have to think about edge cases, like aliases. I guess this can be error prone.
An argument parser seems like the perfect place for this...

@xemle
Copy link

xemle commented Feb 7, 2023

I've stumbled across this issue, too and found a way via the middleware and the yargs instance to identify the usage of default values:

const yargs = require('yargs')

const argv = yargs
  .options({
    'port': {
      alias: ['p'],
      default: 3000,
      number:true
    }
  })
  .middleware((argv, yargs) => {
    console.log(`Defaulted vaules are`, yargs.parsed.defaulted)
    return argv
  })
  .help()
  .argv

console.log(`Port is ${argv.port}`)

Returns

$ node index.js -p 3000
Defaulted vaules are {}
Port is 3000
$ node index.js 
Defaulted vaules are { port: true }
Port is 3000

My question is: Is this an official way since the documentation does not tell something.

@xemle
Copy link

xemle commented Feb 8, 2023

Since yargs.parsed is deprecated you can set a undefined default value with a description and check later if parameter was given:

const yargs = require('yargs')

const argv = yargs
  .options({
    'port': {
      alias: ['p'],
      number:true
    }
  })
  .default('port', undefined, '3000')
  .help()
  .argv

console.log(`Port is ${argv.port}`)

Returns

$ node index.js -p 3000
Port is 3000
$ node index.js 
Port is undefined
$ node index.js -h
Options:
      --version  Show version number                                   [boolean]
  -p, --port                                            [number] [default: 3000]
      --help     Show help                                             [boolean]

@dlong500
Copy link

@xemle Setting an undefined value kind of defeats the purpose of default values. You may as well not have a default value in that case.

@maxrimue Given that it appears yargs.parsed is deprecated, what would your solution be instead of the function you posted?

IMO this does seem to be a feature that should be built in to yargs. It doesn't seem uncommon that config files might be used in conjunction with yargs to resolve the final settings for a CLI app. Without knowing which arguments were explicitly defined on the command line there is no way to resolve whether a setting should come from a config file or the command line. Knowing which arguments were explicitly defined doesn't seem like that type of thing that we should need to hack our way into especially given all the things that yargs already does in regards to aliases and camelCase conversions.

@xemle
Copy link

xemle commented Apr 14, 2023

@dlong500 I agree that undefined as default value is not the best choice.

It doesn't seem uncommon that config files might be used in conjunction with yargs to resolve the final settings for a CLI app.

yargs uses a flat list of arguments which need to be reflected than on the config object via .config(), too. My requirement is to have a nested config object (example) where the CLI args replaces some parts somewhere. So my workflow is currently to merge yargs parameters into the nested config. So I need to know if yargs has an explicit value or I need to use the default value of the nested config.

@dlong500 If you have an idea how to solve nested config with yargs, please let me know.

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

Successfully merging a pull request may close this issue.