SBoudrias / Inquirer.js

A collection of common interactive command line user interfaces.
MIT License
20.29k stars 1.3k forks source link

override like prompt? #166

Closed tonylukasavage closed 4 months ago

tonylukasavage commented 10 years ago

One bit of functionality missing from inquirer that I found indispensable in prompt is the ability to provide an override to the prompting process. So in the case of inquirer, if I have 2 questions I want to ask, but in previous code I've already determined the value of 1 of them, I'd love it if I could do something like this:

var questions = [
    {
        name: 'name',
        type: 'input',
        message: 'name:'
    },
    {
        name: 'value',
        type: 'input',
        message: 'value:'
    },
];
var override = {
    name: 'this is the name'
};
inquirer.prompt(questions, override, function(answers) {
    // process answers
});

In the above case, only the value question would be asked since name is already available in the override. The resulting answers object would include the input answer for value and the value from the override for name.

I didn't see this functionality in the docs nor did I see any means to do it in a very quick check of the source.

SBoudrias commented 10 years ago

There's a lot of way this can be done manually by the user in pretty easy way (build/filter the question array) - or even using parts of inquirer api like when functions.

It is actually a pretty common use case in a lot of yeoman-generators.

Thanks for the suggestion, but I don't think this functionality would add real benefits over filtering the array you pass to inquirer manually.

pyramation commented 7 years ago

can we reopen this?

I'll try to illustrate a good use case:

var inquirer = require('inquirer');
inquirer.registerPrompt(
  'autocomplete',
  require('inquirer-autocomplete-prompt')
);
...
inquirer.prompt([{
  type: 'autocomplete',
  name: 'from',
  message: 'Select a state to travel from',
  source: function(answersSoFar, input) {
    return myApi.searchStates(input);
  }
}]).then(function(answers) {
  //etc
});

If we filter the questions, you will see that answersSoFar will be missing what could be useful for narrowing down a search result, for example. I understand there are hacks around it, but I agree with @tonylukasavage that this is an important missing feature.

SBoudrias commented 7 years ago

Okay, I'll reopen. That's been a feature asked many times.

I'm still unsure how this could cleanly fit in the codebase - and it needs to works properly with plugins.

pyramation commented 7 years ago

cool! I took a stab at it here: https://github.com/pyramation/Inquirer.js/pull/1/files

I think I have something that works, but 3 tests are broken, maybe you can see what I did wrong! The test case I wrote passes---it's something around async fetching of property defaults that broke (error messages at bottom of this post)

My approach was to return an always-resolving answer from the overrides before asking the user anything.

PromptUI.prototype.fetchAnswer = function (question) {
  if (question.name && _.has(this.overrides, question.name)) {
    return rx.Observable.defer(
      function () {
        return rx.Observable.fromPromise(
          new Promise(resolve => {
            resolve({
              name: question.name,
              answer: this.overrides[question.name]
            });
          })
        );
      }.bind(this)
    );
  }

  var Prompt = this.prompts[question.type];
  this.activePrompt = new Prompt(question, this.rl, this.answers);
  return rx.Observable.defer(function () {
    return rx.Observable.fromPromise(this.activePrompt.run().then(function (answer) {
      return {name: question.name, answer: answer};
    }));
  }.bind(this));
};

For providing overrides, I passed them into the prompt constructor:

    var overrides = {
      q1: false
    };
    var prompts = [
      {
        type: 'confirm',
        name: 'q1',
        message: 'message',
        default: true
      },
      {
        type: 'confirm',
        name: 'q2',
        message: 'message',
        default: false
      }
    ];
    var promise = this.prompt(prompts, overrides);

Here were the errors:


  1) inquirer.prompt should run asynchronous `message`:
     Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.

  2) inquirer.prompt should parse `default` if passed as a function:
     Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.

  3) inquirer.prompt should parse `choices` if passed as a function:
     Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.
pyramation commented 6 years ago

finally got around to it - but ended up just making a wrapper. If anyone is interested in this https://github.com/pyramation/inquirerer

GeorgeGkas commented 6 years ago

What is the current state of this? @pyramation gave a solution by creating another package. Do we expect to implement this feature in this package as well? Should we close it?

@tonylukasavage you are the one who opened the issue. What is your opinion?

PsyGik commented 3 years ago

I believe this use case is covered with the following:

const answers = require('./answers'); // This could be from a file, or from some business logic. 
const questions = require('./questions');

inquirer
    .prompt(questions, answers) // provide `answers` as an argument, and inquirer skips those questions
    .then((_answers) => {
      // Use user feedback for... whatever!!
      preFlightChecks(_answers);
    })
    .catch(handleError);

answers.js

module.exports = {
  description: "Default answer, or something specific",
  .
  .
  .
}

Answers will contain objects whose answers are known. When this is passed as args into Inquirer it skips those questions.

questions.js

module.exports =  [
  {
    type: "input",
    name: "title",
    message: "Title",
    default: "Random Title",
  },
  {
    type: "input",
    name: "description",
    message: "Description",
    when: function (answers) {
      return answers.title;
    },
  }