$darkmode
Elektra 0.11.0
|
Most applications don't need the flexibility of the low-level API (with kdbGet
, kdbSet
, etc.). In most cases an easy and safe way to access the configuration values is preferred. This is why we created the high-level API.
There are two different ways of using the high-level API:
The recommended way is via code-generation (which will be explained below). If you want to use the high-level API directly take a look at its documentation. Please note, however, that certain features are only available through code-generation.
The code-generation API builds on Elektra's specifications. Instead of just using specifications at runtime, the code-generator parses them when invoked (ideally right before compiling) and utilizes the specification when generating configuration accessor functions.
The process of writing a full specification for your application is beyond the scope of this guide. We will just focus on the parts that are necessary for using the code-generation API.
We will use this specification in the format of the ni
plugin:
In Elektra a specification is defined through the metadata of keys in the spec
namespace. The specification above contains metadata for three keys:
@
)@/mydouble
@/myfloatarray/#
(The #
at the end of myfloatarray/#
indicates that it is an array)The mountpoint
metadata on the parent key sets the name of our application's config file (the location is defined by Elektra), it should be unique.
The type
metadata specifies the type of key. The available types can be found in the high-level API Readme under "Data Types". It is important to set the type
, because the code-generator will ignore all keys that don't have a type
.
Because we want our getters to be unable to fail (makes error handling trivial), we need to provide a default
value as well. Note that default
s for array keys like myfloatarray/#
only work via the spec
plugin. If you didn't mount everything correctly, you will get an error.
That's it. The code-generator just requires that each key (that you want to access) has a type
and a default
value.
Note: You can also mark keys with the require
metadata, if there is no reasonable default value. This is only recommended as a last resort, but still preserves the guarantee that elektraGet*
calls won't fail. If a require
d key is missing, the initialization of the Elektra
handle will fail.
The code-generator is a very powerful and flexible tool and has many options to tweak its output. If you want to know more about how to set up everything just the way you want to, take a look at the man-pages `kdb-gen(1)` and `kdb-gen-highlevel(1)`.
To get started the basic invocation of the code-generator should be enough:
This tells the code-generator that your application uses the parent key /sw/example/myapp/#0/current
and that the output files should be called conf.*
. The argument highlevel
just specifies which template to use and the option -F ni=spec.ini
indicates that the file spec.ini
(in the ni
plugin's format) contains the specification. While the code-generator can read a specification from the KDB, we recommend you use the -F
option. It keeps the KDB clean and can avoid troubles later on, when installing your application.
You can now take a look at conf.h
and conf.c
(the files generated by the compiler). Depending on the specification you used, these files may be very long. They might also be formatted strangely, because of limitations in the code-generator. Feel free to reformat them with your tool of choice, before inspecting them.
To explain how to use the generated code, lets take a look at some of conf.h
. For brevity's sake some parts of the file have been replaced by placeholder comments.
First there is some boilerplate, including the copyright header, include statements and some helper macros.
Next we see all the 'tag macros' used to refer to config values. These are essentially just aliases, but they allow for some flexibility in how we generate the names of the static inline
functions further down. You should always refer to your config values via these macros, even if they are just aliases. This is because we might have to change the naming scheme for the functions, but we will try to keep the tag macros unchanged.
Additionally, the comments for these macros contain the documentation on what arguments are needed for accessing the tag in question. For example to access the elements of the array myfloatarray/#
, we obviously need to provide an index.
Then we see some local helper macros only used in this file.
This is the most important part of the header. It is what makes the API work.
For each config value we generate an ELEKTRA_GET(*)
and an ELEKTRA_SET(*)
accessor function. All these functions are static inline
, because they just call other getter/setter functions with partially fixed arguments. In fact many of these functions will only be a single line.
Then we undefine the local macros we defined before and declare the three initialization functions loadConfiguration
, printHelpMessage
and exitForSpecload
.
At the end of the file you will find the elektra*
convenience macros. These macros can be used to make accessing config values look more like normal function calls and avoid the ugly double parentheses in e.g. ELEKTRA_GET (...) (...)
.
We start at the bottom of our conf.h
excerpt. exitForSpecload
is used to initiate specload mode, if needed. This mode makes your application provide its specification to Elektra. How this works exactly is not so important (see specload plugin). You only need to know, that exitForSpecload
should be called immediately at the start of your main
function and that it only returns, when your application is not in specload mode.
To access your configuration, you first need to call loadConfiguration
to get an Elektra
handle for your application. This is done via a snippet that is more or less the same for all applications:
Next it is recommended, you change the default handler for fatal errors. By default, we just call exit (EXIT_FAILURE)
, since we don't know how you log your errors and what cleanup may be needed.
onFatalError
will receive the fatal ElektraError *
. It must at least call elektraErrorReset
on the error and then call exit()
.
If you want to try out the application immediately, skip down to the section about compiling. You may also have to follow the section on running your application to get everything up and running.
Once you have your Elektra
instance, reading config values is easy. You just call one of the getter functions.
Here we used the convenience macro elektraGet
. You could also invoke the static inline
accessor function directly:
No error handling is required, because getter functions are designed to not fail. In a correct setup, either the initialization fails and getters are never called, or getter calls always succeed.
To access config values that don't have a static key name, like arrays, you have to supply additional arguments (and use elektraGetV
):
Of course, we also need to know how big the myfloatarray/#
array actually is. To that end we can use ELEKTRA_SIZE
or elektraSize
:
ELEKTRA_SIZE
functions like their ELEKTRA_GET
counterparts are designed to not fail.
Please note that even if you set up everything correctly, calling a getter on a non-existent, wrongly typed or otherwise inconvertible key is a fatal error. All fatal errors result in a call to the fatal error handler and therefore will exit the application.
Writing config values is not quite as easy as reading, but it is still quite simple:
As you can see the complexity stems from the necessary error handling. Because setting values involves IO and other uncontrollable factors, setter calls cannot be designed to not fail. This is why they accept an additional ElektraError **
argument. It is important to call elektraErrorReset
, if an error was set. Calling a setter with a non-null ElektraError **
parameter is a fatal error.
Of course, you can also use elektraSet
(error handling omitted):
Note that elektraSetV
takes the ElektraError
argument before the variable arguments, while in ELEKTA_SET
the error is always the last argument. This is because of limitations in the C macro system.
There is no setter for array sizes. Since Elektra's low-level part supports discontinuous arrays, we simply change the array size whenever necessary, if an array element setter is called. However, the high-level API has no support for discontinuous arrays, so take care not to create holes in your arrays, if you want to iterate over them. Remember, accessing non-existent keys (and this includes array elements) is a fatal error.
The generated loadConfiguration
function automatically mounts the gopts
plugin. This means that command-line options (as described here) are parsed and their values are set on the corresponding keys. You don't have to do anything, apart from setting the opt
metadata. The only exception to that is the help mode.
When your application is called with -h
or --help
, we enter help mode. This is indicated by the return value 1
of loadConfiguration
. As you can see in the example above, you should call printHelpMessage
to print an appropriate help message to stdout
and then close the allocated Elektra
instance and exit
. Beware an Elektra
instance created in help mode may not be fully functional, which is why you should immediately close it once you called printHelpMessage
.
The code-generator has some more advanced features that are supported out of the box. For example, you can read multiple config values at once by utilizing structs. The use of structs also allows for recursive configurations like menus (a menu can have submenus).
For more information take a look at the man-page `kdb-gen-highlevel(1)`.
Once you've written your application, you will want to compile it. This requires linking some libraries and adding to your include path. The easiest way is to use CMake or pkg-config to find the needed compiler options. Examples on how set this up can be found in here and here.
The compiler invocation should look something like this:
Note: At least C99 is required, so if your compiler defaults to an older version you'll need to add -std=c99
.
Running your application is easy, just run the executable (e.g. myapp
). While this might work out of the box, you will just get the default configuration. To change the configuration you need to use kdb
, which doesn't know about your specification yet. This means you would need to set the type
metadata and all the other stuff that your application expects by hand. For every single key. Obviously this is not the right solution.
A better solution is to inform Elektra (and kdb
) about our specification. Then Elektra automatically copies metadata to where it should be.
First you need to mount your specification itself into the KDB. Mounting is basically the process of informing Elektra about a new part of the KDB, similar to how mounting an external hard drive informs the OS about a new part of the file system.
The command above assumes that you also used the kdb gen
command from above and that the myapp
executable is located in $PWD
.
Note: Because of a limitation in
specload
, we have to use thenoresolver
resolver. This also means that the path to the config file (here/etc/myapp_spec.eqd
) has to be absolute. Otherwise, it will always be relative to the current working directory in whichkdb
or your application was executed. The file should not exist when callingkdb mount
.specload
works different to other plugins. The given config file is only used, if the user makes changes to the specification viakdb set
.
Now that Elektra knows about your specification, calling your application might work better since metadata should now be copied when you set a config value via kdb set
. However, there won't be any type checking. For that we need to enable the type
plugin. While this could be done manually, we can just let Elektra figure out which plugins we need and activate all of them.
This can be done with the spec-mount
command:
Now finally your application is all setup.
To configure your application you can use kdb
:
If you want to set a value system-wide (not just for your user) you can use the system namespace:
Always use the cascading version of kdb set
(i.e. the keyname begins with a slash /
), otherwise type checking and other plugins might not be called correctly.