Airline 3.0.0 has several breaking changes
Java 11 is now required as the minimum Java version. This was necessary in order to be able to continue to use up-to-date releases of our dependencies, and since Java 8 is now EOL.
In order to avoid package collisions between modules and enable the airline-help-bash
module to become a JPMS module
this annotation was moved into a new sub-package com.github.rvesse.airline.annotations.help.bash
As noted in Migrating to Airline 2.9 Airline is moving away from usage of the @Inject
annotation for composition in favour of its own @AirlineModule
annotation. As part of this move the jakarta. inject-api
and airline-backcompat-javaxinject
dependencies became optional
in 3.0.0
. This means that if you have
an existing Airline based application that is using @Inject
annotations you MUST now provide the relevant
dependency yourself as you will not automatically pick it up as a transitive dependency of Airline.
You MUST also explicitly configure the @Parser
configuration to use the old composition annotations if you still
need to use these.
Airline 2.9 introduces some changes to composition annotations in preparation for breaking changes in future releases.
In particular unless you use Airline in conjunction with a dependency injection framework you should stop using the
@Inject
annotation on command fields and transition to using the @AirlineModule
annotation instead.
For backwards compatibility purposes usage of the older @Inject
annotation remains supported for the time being
BUT this will require explicit user configuration in future releases.
Users who are able to transition to @AirlineModule
may wish to explicitly exclude the following dependencies when
depending on Airline to avoid polluting their classpath with unnecessary dependencies if they no longer need @Inject
present:
<exclusions>
<exclusion>
<groupId>jakarta.inject</groupId>
<artifactId>jakarta.inject-api</artifactId>
</exclusion>
<exclusion>
<groupId>com.github.rvesse</groupId>
<artifactId>airline-backcompat-javaxinject</artifactId>
</exclusion>
</exclusions>
Airline 2.2 has some minor breaking changes versus Airline 2.1
Previously @Arguments
had an arity
field that was used to control the maximum arity of arguments. In 2.2 this is
removed and you should use the @MaxOccurrences
restriction annotation to achieve the same effect e.g.
@Arguments
@MaxOccurrences(occurrences = 3)
private List<String> args = new ArrayList<>();
The airline-help-ronn
module was removed in 2.2 as it was already deprecated. For Markdown format help use the
airline-help-markdown
module and for Man pages uses the airline-help-man
module.
Previously global restrictions for CLIs could only be specified via the restrictions
field of the @Cli
annotation.
As of Airline 2.2 the global restrictions can now be specified via annotations in the same way that option and argument
restrictions are specified.
Airline 2.2 supports the following global restriction annotations:
@CommandRequired
@NoUnexpectedArguments
@NoMissingOptionValues
@None
Note that most users will not need to use these annotations as the @Cli
annotation has the
includeDefaultRestrictions
field default to true
which causes the default set of global restrictions to be included
without any further user configuration.
Airline 2.1 includes a number of breaking changes which are part of improvements and restructuring of the help systems.
Previously all help generators were included in the main airline
module. With Airline 2.1 only the CLI help is
included in that module, all other help generators are provided by separate modules which can be pulled in as desired.
Help Generators | New Module |
---|---|
RONN (Deprecated in 2.1) | airline-help-ronn |
HTML | airline-help-html |
Bash Completion | airline-help-bash |
Man (New in 2.1) | airline-help-man |
Markdown (New in 2.1) | airline-help-markdown |
If you were using any of these then you may need to tweak your dependencies appropriately.
Previously we provided RONN format help generators which generated RONN output (an extended dialect of Markdown) that
could then be converted into man
pages with the ronn
tool. However we found that RONN had a number of bugs that
could cause the generated man pages to have strange formatting in some cases.
To address this new Man format help generators are introduced which are capable of generating man pages directly without needing an intermediate format or third party tool. As a result the existing RONN generators are deprecated and should be avoided in favour of the new help generators.
Since RONN generators are now deprecated we have also added new Markdown format help generators which can be used to generate Markdown format help directly. These don't use any of the extended RONN syntax though they do use GitHub Flavoured Markdown table syntax for tables.
Previously information for Bash completion was provided by the completionBehaviour
and completionCommand
fields of
the @Option
and @Arguments
annotations. In 2.1 these are now moved into their own @BashCompletion
annotation
which is now found in the airline-help-bash
module.
For example in Airline 2:
@Option(name = { "--example" }, arity = 1, completionBehaviour = CompletionBehaviour.FILES)
private String example;
Would change to the following in Airline 2.1:
@Option(name = { "--examples" }, arity = 1)
@BashCompletion(behaviour = CompletionBehaviour.FILES)
private String example;
In Airline 2.0 restriction factories had to be explicitly registered with the RestrictionRegistry
, Airline 2.1 moves
to using ServiceLoader
for discovery of restriction factories. While factories may still be explicitly registered
they can now also be specified in the following files:
META-INF/services/com.github.rvesse.airline.restrictions.factories.OptionRestrictionFactory
META-INF/services/com.github.rvesse.airline.restrictions.factories.ArgumentsRestrictionFactory
Note that if you want to use this mechanism and intend to use the Maven shade plugin or similar to build a single JAR
you must ensure that you preserve the built-in services files from the airline
module as otherwise the built-in
restriction annotations will not be honoured.
In Airline 2.0 help section factories had to be explicitly registered with the HelpSectionFactory
, Airline 2.1 moves
to using ServiceLoader
for discovery of help section factories. While factories may still be explicitly registered
they can now also be specified in the following file:
META-INF/services/com.github.rvesse.airline.help.sections.factories.HelpSectionFactory
Note that if you want to use this mechanism and intend to use the Maven shade plugin or similar to build a single JAR
you must ensure that you preserve the built-in services files from the airline
module as otherwise the built-in help
section annotations will not be honoured.
Additionally the HelpSectionRegistry
was moved into the com.github.rvesse.airline.help.sections.factories
package.
Airline 2 is a significant rewrite of Airline and as such there are lots of breaking changes to be aware of. This document aims to guide you through the user facing changes.
Note that if you are developing in the internals of Airline there are lots of other changes which are not covered here since they are not visible to end users.
The major change in defining CLIs using the CliBuilder<T>
is that all parser related options are now moved onto a new
ParserBuilder<T>
. This builder can be accessed via the withParser()
method.
So for example the following Airline 1 code:
CliBuilder<ExampleRunnable> builder
= Cli.<ExampleRunnable>builder("cli")
// Add a description
.withDescription("A simple CLI with several commands available in groups")
// We can enable command and option abbreviation, this allows users to only
// type part of the group/command/option name provided that the portion they
// type is unambiguous
.withCommandAbbreviation()
.withOptionAbbreviation();
Would need changing to the following:
CliBuilder<ExampleRunnable> builder
= Cli.<ExampleRunnable>builder("cli")
// Add a description
.withDescription("A simple CLI with several commands available in groups");
// We can enable command and option abbreviation, this allows users to only
// type part of the group/command/option name provided that the portion they
// type is unambiguous
builder.withParser()
.withCommandAbbreviation()
.withOptionAbbreviation();
You can now also define GlobalRestriction
s using the withRestriction()
or withRestrictions()
methods on your
CliBuilder<T>
. If you don't define any restrictions a default set that provides backwards behavioural compatibility
with Airline 1 is used. This default set can also be explicitly added by calling withDefaultRestrictions()
on your
builder.
Previously CLIs could only be defined via the CliBuilder<T>
, Airline 2 introduces a new @Cli
annotation that can be
used to define a CLI via a class annotation.
For example the same example as the above could now be defined like so:
@Cli(name = "cli",
description = "A simple CLI with several commands available in groups",
parser = @Parser(allowOptionAbbreviation = true, allowCommandAbbreviation = true))
A class annotated in this way can be used to create a Cli
instance like so:
Cli<ExampleRunnable> cli = new Cli<ExampleRunnable>(AnnotatedClass.class);
Previously you could not specify parser options when defining single commands, you are now able to pass in a
ParserMetadata<T>
in order to specify parser options for single command parsing.
Alternatively you can add the @Parser
annotation to your class and this will be automatically detected and used unless
you pass a ParserMetadata<T>
explicitly to the singleCommand()
method.
You may now also pass in the GlobalRestriction
s that should apply, if you don't define any restrictions a default set
that provides backwards behavioural compatibility with Airline 1 is used
All annotations are now located in the com.github.rvesse.airline.annotations
package or in sub-packages thereof.
Some extended help fields are no longer available directly on the @Command
annotation and instead now have
their own specific annotations:
examples
moved to@Examples
discussion
moved to@Discussion
exitCodes
andexitCodeDescriptions
moved to@ExitCodes
For example discussion in Airline 1:
@Command(name = "example", discussion = { "This is some discussion" })
public class MyCommand {
}
Becomes the following in Airline 2:
@Command(name = "example")
@Discussion(paragraphs = { "This is some discussion" })
public class MyCommand {
}
Some fields of the @Option
annotation that defined restrictions on options have been removed, namely these are the
required
, allowedValues
and ignoreCase
attributes. Option restrictions are now instead expressed with specific
annotations such as @Required
.
When overriding existing options restrictions are inherited from the parent unless new restriction(s) are defined on the
overridden option. If you simply want to remove all existing restrictions when overriding an option you can add the
@Unrestricted
annotation to the overridden option.
For example a required option in Airline 1:
@Option(name = "--example", arity = 1, required = true)
private String example;
Becomes the following in Airline 2:
@Option(name = "--example", arity = 1)
@Required
private String example;
Similarly an option with allowed values in Airline 1:
@Option(name = "--example", arity = 1, allowedValues = { "a", "b", "c" })
private String example;
Becomes the following in Airline 2:
@Option(name = "--example", arity = 1)
@AllowedRawValues(allowedValues = { "a", "b", "c" })
private String example;
Similar to @Option
the @Arguments
annotation has changed to have the required
field removed, as with options you
can now simply add the @Required
annotation instead to indicate that arguments are required.
Arguments now allows for applying many different restrictions such as @AllowedRawValues
to arguments.
All exceptions are now located in the com.github.rvesse.airline.parser.errors
package of sub-packages there-of.
The GlobalMetadata
class is now a generic type i.e. GlobalMetadata<T>
which provides better type safety but may
require changing the type signatures of existing code you have written against Airline 1
A new getRestrictions()
method provides access to GlobalRestriction
instances which represent global restrictions on
the CLI.
Parser settings were previously held directly on GlobalMetadata
, they are now held in a ParserMetadata<T>
class
which is accessed via the getParserConfiguration()
method on GlobalMetadata<T>
The extended help details are no longer expressed directly as fields but as instances of HelpSection
. The
getHelpSections()
method provides access to the extended help sections present for a command.
Restrictions on options such as required
are no longer expressed directly as fields but as instances of
OptionRestriction
. The isRequired()
method on OptionMetadata
will check if the IsRequiredRestriction
is present
for an option.
A getRestrictions()
method provides access to all the restrictions that are present for an option.
Restrictions on arguments such as required
are no longer expressed directly as fields but as instances of
ArgumentsRestriction
. The isRequired()
method on ArgumentsMetadata
will check if the IsRequiredRestriction
is
present for the arguments.
A getRestrictions()
method provides access to all the restrictions that are present for arguments.
A new restrictions framework is introduced which allows much more complex restrictions to be specified and enforced.
A few examples are given in the following table, there are many more new restrictions supported by Airline 2 than just these shown here:
Restriction Class | Applicability | Annotation Class | Restriction |
---|---|---|---|
IsRequiredRestriction |
Options and Arguments | @Required |
Indicates that options/arguments are required |
AllowedRawValuesRestriction |
Options and Arguments | @AllowedRawValues |
Indicates that the raw string values passed to options/arguments must be in a given set of values. May be configured to use custom locale and case insensitive comparisons as desired |
RangeRestriction |
Options and Arguments | @LongRange , @IntegerRange , @ShortRange , @ByteRange , @DoubleRange , @FloatRange |
Indicates that the instantiated values for options/arguments must fall within a range of values |
None |
Global, Options and Arguments | @Unrestricted |
Indicates that no restrictions apply, primarily useful if you need to override restrictions inherited from a parent option |
Option and argument restrictions are automatically discovered by examining the annotations present on fields marked with
@Option
and @Arguments
. The RestrictionRegistry
is used to map annotations into instances of OptionRestriction
or ArgumentsRestriction
as appropriate.
Global restrictions are defined when you create a Cli
or SingleCommand
instance, if you don't define any the default
set which provides backwards compatibility with the global restrictions that applied in Airline 1 are used.
The ParseRestrictionViolatedException
serves as the parent exception type for all exceptions pertaining to restriction
violations.
You can create your own custom restrictions by doing the following:
- Create a class that implements
OptionRestriction
and/orArgumentsRestriction
and enforces your restriction. Each restriction interface has a different method signature so a restriction can be implemented that applies to multiple things. - Create an annotation that you will use to denote your restriction. Remember to specify the retention for your
annotation as
RUNTIME
- Create a class that implements
OptionRestrictionFactory
and/orArgumentsRestrictionFactory
. This class does the work of converting your annotation into a restriction instance - Register your restriction:
RestrictionRegistry.addOptionRestriction(MyAnnotation.class, new MyRestrictionFactory());
You can now apply your annotation to the option/arguments field you want to restrict.
Extended help for commands is now specified via the HelpSection
interface. This interface allows for supporting a
variety of different extended help formats and the built-in help generators will automatically include discovered help
sections in their outputs.
This interface is itself derived from the more basic HelpHint
interface. This interface is typically implemented by
other classes that serve some function as well as provided some extended help such as OptionRestriction
implementations. Again the built-in help generators will automatically include restrictions which provide help hints
when formatting help for options and arguments.
Similar to restrictions extra help sections are automatically discovered by examining the annotations present on classes
marked with @Command
. The HelpSectionRegistry
is used to map annotations into instances of HelpSection
.
Help section annotations are automatically inherited so for example if all your commands have a common set of exit codes you could define these once as an annotation on a super-class and all derives command classes would automatically include that exit code section. Where annotations for the same section (same section being determined by having the case-insensitive same title) are defined multiple times in the class hierarchy the definition lowest in the hierarchy wins i.e. a derived class can always override help sections inherited from their parents.
If you are extending an annotated class and want to hide a section that you would normally inherit you can do so by
adding the @HideSection(title = "foo")
annotation where foo
is the title of the section you wish to hide.
You can create your own custom help section by doing the following:
- Create a class that implements
HelpSection
, you can derive fromBasicSection
to get yourself started - Create an annotation that you will use to denote your help section. Remember to specify the retention for your
annotation as
RUNTIME
- Create a class that implements
HelpSectionFactory
. This class does the work of converting your annotation into aHelpSection
instance - Register your help section:
HelpSectionRegistry.addFactory(MyAnnotation.class, new MySectionFactory());
You can now apply your annotation to command classes for which you wish to provide extra help.
Airline 1 supported only three option parsing styles in the following preference order:
- Classic GNU GetOpt i.e.
-nFoo
- Long GNU GetOpt i.e.
--name=Foo
- Whitespace separated ie.
--name Foo
In this release option parsing is now separated out into its own extensible OptionParser
interface that allows you to
customise how options are parsed. You can call withOptionParser()
or withOptionParsers()
on your ParserBuilder<T>
to specify the option parsers to use in your desired preference order.
You can also call withDefaultOptionParsers()
to start from the default Airline 1 setup and then add your own, or call
this after adding your own to use the default setup as your fallback. If you don't explicitly configure any option
parsers then the default Airline 1 setup is used automatically.
A couple of other parsing styles are now available but not enabled by default:
ListValueOptionParser
- Supports parsing options specified in the form--name a,b,c
where the values is split on a separator (default,
but configurable as desired) and passed to the option. This parser is strict in that it requires users invoking commands to provide list values with the correct number of entries presentMaybePairValueOptionParser
- Supports parsing options of arity 2 where they may be specified either as--name a=b
or--name a b
i.e. where the user may specify the values either separated by whitespace or some separator (default=
but configurable as desired)
In Airline 1 how raw string values were converted into the appropriate Java types was fixed, with Airline 2 you can now
configure the TypeConverter
instance to use for this as desired. This can be set via the withTypeConverter()
method
on your ParserBuilder<T>
instance.
Creating a custom TypeConverter
implementation allows you complete control over how you want to convert raw string
values into Java types.
In Airline 1 aliases could not refer to other aliases, in Airline 2 this behaviour may be enabled and it permits aliases to reference each other unless a circular reference is generated during alias resolution.
To enable this you can call withAliasesChaining()
on a ParserBuilder<T>
or create an appropriate ParserMetadata<T>
instance yourself with the appropriate parameter set to true
.