Motivation
Moving towards a 1.0.0 release for Bootstrap we generated some new ideas for usage scenarios of the tool.
We would like Bootstrap to be usable in the following scenarios:
- As a command line tool (as originally intended). Note that command and argument completion was still an issue.
- Integrated in Bndtools, generating dialogs and such
- Scripted; fully leveraging Gogo's scripting capabilities
This poses some challenges on how Bootstrap deals with command arguments and interactive questions. The main problem is that we need to static information about the possible questions for a command to be able to satisfy all requirements.
Besides these news requirements we also found some usability issues with the current commands:
- Some commands accept arguments, others don't
- Arguments (when supported) are not named. It would be better to have Posix like arguments
- It's easy to get multiple plugins registering the same command names, which is confusing
After much discussion and many prototypes Marcel Offermans (Deactivated) and Paul Bakker came up with the API described on this page. As always this is a game of trade-offs, but this approach feels very usable.
Introducing the new API
From now on commands are implemented in their own class. This gives more opportunity to statically describe the capabilities of the command. If may require a bit more code for plugins that offer multiple commands that are similar; but in practice this doesn't seem to be a big issue. Also, it offers better code separation of command implementations.
A command can now be implemented as follows:
@Component public class LsCommand implements Command<List<File>> { @ServiceDependency private volatile Navigator m_navigator; @Override public String getName() { return "ls"; } @Override public Plugin getPlugin() { return BootstrapPlugin.INSTANCE; } @Override public Scope getScope() { return Scope.WORKSPACE; } @Override public List<Question<?>> getQuestions() { return Arrays.asList( new BooleanQuestion("R", "recursive", false), new BooleanQuestion("F", "files only", false), new StringQuestion(Question.DEFAULT_KEY, "filter", "")); } @Override public List<File> execute(String... args) { Answers answers = Answers.parse(getQuestions(), args); FileFilter filter = null; boolean recursive = answers.get("R"); boolean filesOnly = answers.get("F"); String nameFilter = answers.get(Question.DEFAULT_KEY); //Do stuff } }