Command Line Support#
Provided as a default, the ApplicationPropertiesUtilities class
includes the add_default_command_line_arguments function that provides
three arguments to the command line:
--configto specify a single configuration file--setto specify a single configuration item--strict-configto enable strict mode
While users are free to specify their own command line arguments and names, the following sections will use these arguments as a baseline.
Command Line Configuration Files#
The most visible way to specify a configuration file is to use the
command line --config argument. That argument specifies a relative or absolute
path to the configuration file to load. The configuration file may be in any one
of three formats discussed in the document on configuration file types.
Keep in mind that configuration files are firmly in the middle when it comes
to configuration layers
that the application_properties package supports. Especially when debugging
and focusing on other things, it can be easy to forget that there are other
configuration layers that will override the configuration items in the configuration
file. Trust us... it has happened multiple times to our team.
Command Line Configuration Settings#
Configuration items set from the command line are the most specific form
of configuration. Unlike any of the configuration items in configuration files,
these settings are specifically targeted to a specific invocation of the application
that is using the application_properties package.
These arguments come in two classes: a specific command line setting and a general command line setting. To illustrate the usage of command line arguments to set configuration items, the following configuration items from PyMarkdown are used as a reference point:
log.level- string value ofINFOplugins.md007.code_block_line_length- integer value of160plugins.md007.enabled- boolean value oftrueextensions.front-matter.enabled- boolean value oftrue
Specific Command Line Settings#
Specific command line settings are settings that are provided by the command
line as a shortcut for a longer configuration item or aggregated set of configuration
items.
A good example of a shortcut argument is a --log-level INFO command line argument
being a
shortcut for --set mode.log-level=INFO. A good example of an aggregator
argument is a --profile argument that accepts a single argument that applies
multiple configuration items when invoked. In both cases, the specific command
line argument is presented as a shortcut for the user's benefit.
Trying to determine which configuration items should have a more specific way of setting their values was not an easy task for our PyMarkdown team. Enabling every setting to have a direct command line invocation is not feasible, so we had to derive a guideline for when a configuration item would merit its own command line argument. This is what we came up with.
The first criteria that we decided on was to determine how frequently
that configuration item would be used. A good example of this is the
--log-level and --log-file arguments. We figured that out of all
the configuration items in most applications, controlling the log output
was probably one of the top three most used configuration items. From there,
we tried to
hit any other frequently used configuration setting, giving each a command line
argument.
The second criteria we used was to determine whether the configuration item
changed the behavior of the core application, or if it was related to one
of the features or plugins applied to the application. For PyMarkdown,
a splendid example of this is the --return-code-scheme specific command line
setting. This setting directly changes how return codes are returned from
the core PyMarkdown engine, something we believed made it eligible for
being a general command line argument.
Your team may have their own criteria about what to expose as a detailed command line setting in your application. We just wanted to share our criteria to give your team a good starting point for any discussions.
General Command Line Settings#
General command line settings use the -s or --set argument to
specify a single configuration property to set the value for. The configuration
item key is identified using
its flattened hierarchical format
and an optional configuration type
for the configuration item value. If no configuration type is provided, the property
uses a default type of string.
These two concepts work together to provide a condensed way to specify configuration
properties from the command line.
For the PyMarkdown development team, these arguments have proven to be useful in providing shorthand for setting properties that are isolated and near the top of the configuration item specificity list. Because of its high ranking in the configuration layers due to its specific nature, these settings are less likely to be overridden. For example, a common command line that we use to display enhanced logging output is:
bash
pymarkdown --stack-trace --set log.level=INFO scan examples
While a handful of configuration items have general command line settings, most configuration items do not.
Configuration Item Types#
To provide a more robust configuration system, the configuration
manager uses values that are typed. This extra
level of specification allows increased confidence that the value that
is provided for that configuration item is interpreted properly.
If you are using a configuration file format that provides type information,
this extra information is not required. However, as the command line
does not provide this type information, our team needed to develop
a notation that indicates what type to apply to the command line --set argument.
The type specification notation is as a prefix that the user applies for the configuration
item value. If the * character refers to any character, the following
table specifies the type behavior:
| Prefix | Type | Examples |
|---|---|---|
* or None |
Default (String) | abc |
$* (except for characters below) |
Default (String) | $abc |
$$ |
String | $$abc |
$# |
Integer | $#1, $#-12345 |
$! |
Boolean | $!True, $!anything-else-is-false |
The only two interpretations that likely require further explanation are
the integer and the boolean types. The integer type attempts to
translate any characters past the prefix as a signed integer. The
boolean type compares any characters past the prefix in a case-insensitive
manner against the sequence true, setting the configuration item's value to True
only if that comparison is true.
For our team, the decision to compare boolean values against true was an
easy one, with precedents in other tools. But applying similar rules
to the integer conversion was not possible. After thinking things through, we
landed on generating a ValueError for any invalid integer values, such as $#1.1.
There were alternatives, but this was the decision that felt the most
correct to us. If a user took the time to use the integer prefix $#,
we felt they would want to error out on any non-integer value.
Special Characters and Shells#
On a Windows system, when entering configuration item type arguments that specify
a boolean type, the ! character is used. Since this character is a special character,
you need to enter it as ^^! on the command line to properly escape the ! character.
text
pipenv run pymarkdown --set extensions.front-matter.enabled=$^^!True scan -r .
On a Linux or MacOs system, most shells treat the $ character as a special
character. To escape this character, you need to enclose the argument with the
' character instead of the normal " character.
text
pipenv run pymarkdown --set 'extensions.front-matter.enabled=$!True' scan -r .
Typing Examples#
Using actual PyMarkdown configuration items, examples of typing in action are:
-
indicate that the logging level should be set to show information log messages or higher (string value):
pymarkdown --set log.level=INFO scan test.mdOR
pymarkdown --set log.level=$INFO scan test.mdOR
pymarkdown --set log.level=$$INFO scan test.md -
enabling the extension to interpret front matter (boolean value):
pymarkdown --set extensions.front-matter.enabled=$!True scan test.md -
setting the maximum line length for code block lines to
160(integer value)pymarkdown -s plugins.md007.code_block_line_length=$#160 scan test.md
Strict Configuration Mode#
During the development of the PyMarkdown linter, there were specific times where we wanted to be sure that the configuration values that we specified were interpreted exactly as we specified them. As we started doing exploratory testing of the PyMarkdown project, we also realized that we are sticklers, wanting to ensure that any configuration properties that we assign are correct and that those values are not reverting to default values.
However, our team's desire for that level of exactness seemed to be at cross purposes to our decision to provide for a configuration manager that would work by default. That is where the genesis of the idea that would become the configuration manager's strict mode was formed.
Specified from the command line using the --strict-config flag (or
through the configuration as mode.strict-config=$!True), this
configuration item turns on the strict mode for the configuration system.
Once enabled, when the application reads values from the configuration,
it will stop the application if:
- the user provided a value for the configuration value, but it was the wrong type
- the user provided a value for the configuration value, but it does not match a specified filter for that value
Using the log.level example from above, the documentation states that it is required
to be a string in the following set: CRITICAL, ERROR, WARNING, INFO,
or DEBUG. Therefore, both an integer value of 1 and a string value of
information would fail for distinct reasons. Normally, these would
cause the configuration system to silently fail and not set the specified
value. As a result of that, the default value for that configuration
value will be used with no warning being issued.
However, with the strict configuration mode enabled, the following command line:
sh
pymarkdown --strict-config -s log.level=$#1 scan examples
produces the following output:
text
Configuration Error: The value for property 'log.level' must be of type 'str'.
and the following command line:
sh
pymarkdown --strict-config -s log.level=information scan examples
produces the following output:
text
Configuration Error: The value for property 'log.level' is not valid: Value 'information' is not a valid log level.
This feature is more burdensome for the user, but it is provided as an option for those users who want to make sure their provided configuration items are adhered to exactly.