######## Dictlets ######## The most generic definition of *dictlet* is: an item that can be queried for metadata. For all practical intends and purposes this definition is silly though, as currently *luci* only supports *dictlets* that are text files. I'll leave that generic definition here to indicate that, in theory, every *thing* could theoretically be a *dictlet*: a folder, a url, a jpeg... But, for now, let's just assume a *dictlet* is a text file that is enriched with some metadata that is not necessary for it's primary purpose. - Development_ - `Adding metadata`_ - `Debugging`_ - `Command-line interface basics`_ Development =========== Adding metadata --------------- 'Developing' a dictlet means adding structured metadata to it, in a way that doesn't affect it's original function. Practically that means adding comments that *luci* can read and interpret as a dictionary. *luci* uses so-called :doc:`dictlet readers ` to extract relevant metadata. The only implemented reader at the moment is the :class:`~luci.readers.YamlDictletReader`, which inherits most of it's logic from the base class :class:`~luci.readers.TextFileDictletReader`. This reader checks a text file for certain marker string, and then extracts the metadata according to a set of rules: - check if the file contains the ``lucify`` keyword. if it does not: - if the file doesn't contain the ``lucify`` keyword, check if it contains the default 'root' key for the *lucifier* that is used at the moment (most often that keyword is ``__luci__``, but for example the :class:`~luci.lucifiers.DownloadLucifier` uses ``url`` -- check the source code of the lucifier in question for details) - if neither the ``lucify`` keyword is found, nor the lucifier specific keyword, *luci* will read the whole file - now, the file is read in this way: - store the characters that precede the keyword (on the line that contains it) as prefix (if any) - read all lines below that keyword into a buffer, removing the prefix for each line (usually those are comment indicators) - ignore all lines that do not start with the prefix - stop once the end of the file is reached, or a line contains the two keywords ``lucify`` and ``stop`` - hand the content of the buffer to the right reader sub-class, which parses the text into a dictionary So, let's say we have a *csv* file (``people.csv``) with the following content: .. code-block:: none # lucify # author: Markus Binsteiner # date: "2018-03-03" # tags: # - luci # - example date,subject,result 2018-01-01,alice,okish 2018-01-02,tom,gone crazy Using the *luci* ``metadata`` lucifier, we would extract the following metadata: .. code-block:: console ❯ luci metadata text_file_metadata_example.csv author: Markus Binsteiner date: '2018-03-03' tags: - luci - example The reader stops reading, once it doesn't find a line starting with ``#`` anymore. We can make it stop beforehand though, if we want: .. code-block:: none # lucify # author: Markus Binsteiner # date: "2018-03-03" # lucify: stop # tags: # - luci # - example This would stop after the *date* key. The ':' character after ``lucify`` is optional. We can also split up our metadata into two blocks if we want to, getting the same result as above: .. code-block:: none # lucify # author: Markus Binsteiner # date: "2018-03-03" date,subject,result 2018-01-01,alice,okish 2018-01-02,tom,gone crazy # tags: # - luci # - example To control *luci* behavior like the adding of a command-line interface, we put the metadata relevant to this under the special ``__luci__`` key. Like so: .. code-block:: none #! /usr/bin/env luci # # __luci__: # __default_lucifier__: template # vars: # - he # - she # What he said: {{ he }} Then, she said: {{ she }} The ``__default_lucifier__`` key is optional, but handy if you use the *luci* she-bang (``#! /usr/bin/env luci``) as it tells *luci* which *lucifier* to use in this case (unfortunately, it's not possible to specify more than one argument in a she-bang line). Everything under ``var`` determines how the command-line interface for the *dictlet* is rendered. As this behavior can be controlled quite detailed, check out the :ref:`section below ` and/or the relevant :doc:`help page `. Debugging --------- Until you get familiar with *dictlets*, it's quite likely you get the formatting wrong, miss a whitespace, or use the wrong key-word. In order to help with those issues, you can use both the ``metadata`` and ``debug`` lucifiers. Let's assume we have a (very weird) dictlet (called ``debug_example``) like this: .. code-block:: none # lucify: start # # defaults: # default1: value1 # default2: value2 # help_string: "{{:: defaults.default2 ::}} help string" # user_default: 42 # # __luci__: # defaults_luci: # luci1: "{{:: defaults.default1 ::}}" # doc: # help: "{{:: defaults.help_string ::}} for dictlet" # short_help: "dictlet short help example" # epilog: "dictlet epilog example" # vars: # __user_input__.arg1: # meta: # alias: number # type: list # required: false # doc: # help: {{:: defaults.help_string ::}} # short_help: {{:: __luci__.defaults_luci ::}} # cli: # enabled: true # option: # type: int # multiple: false # default: {{:: defaults.user_default ::}} # lucify: stop And the user said: "{{ __user_input__.arg1 }}" We can get the resulting metadata, with and without user input using the ``metadata`` lucifier: .. code-block:: console ❯ luci metadata debug_example __user_input__: arg1: 42 defaults: default1: value1 default2: value2 help_string: value2 help string user_default: 42 ❯ luci metadata debug_example --number 12 __user_input__: arg1: 12 defaults: default1: value1 default2: value2 help_string: value2 help string user_default: 42 This gives us the 'useable' metadata we could use for example when doing ``template``-ing: .. code-block:: console ❯ luci template debug_example And the user said: "42" ❯ luci template debug_example --number 12 And the user said: "12" If we need more information, about what goes on internally, we can use the ``debug`` lucifier. This lucifier comes with a lot of options to control the values to display (``luci debug --help``_). Here's how to display the variables resulting from user input: .. code-block:: console ❯ luci debug -mu debug_example --number 12 metadata: user vars: __user_input__: arg1: 12 .. _cli_basics: Command-line interface basics ----------------------------- Getting a *dictlet* (called ``minimal_arg``) to create a command-line interface can be as easy as adding: .. code-block:: none # __luci__: # vars: # - arg1 # - arg2 This adds two command-line options, named ``arg1`` and ``arg2``, which both are optional. This is what it looks like when called with the ``metadata`` lucifier: .. code-block:: console ❯ luci metadata minimal_arg --help Usage: luci metadata minimal_arg [OPTIONS] Options: --arg1 TEXT n/a --arg2 TEXT n/a --help Show this message and exit. User input will go into the keys with the same name: .. code-block:: console ❯ luci metadata minimal_arg --arg1 value1 arg1: value1 Now we can make one of the arguments mandatory, like so: .. code-block:: none # __luci__: # vars: # - arg1 # - arg2: # meta: # required: true We use the ``meta`` key to provide generic information about the option. There is another key, ``cli`` which is used for more command-line specific properties of a cli parameter. Let's call the above *dictlet* without the required option: .. code-block:: console ❯ luci metadata minimal_arg --arg1 value1 Usage: luci metadata minimal_arg [OPTIONS] Error: Missing option "--arg2". And we can give the other argument a default value: .. code-block:: none # __luci__: # vars: # arg1: # meta: # default: VALUE_1 # arg2: # meta: # required: true Notice how the value of the ``var`` key is a dict now, instead of a list. *luci* doesn't really care which you use. Let's try to call the command again, providing ``arg2`` for a change: .. code-block:: console ❯ luci metadata minimal_arg --arg2 value2 arg1: VALUE_1 arg2: value2 If you need more fine-grained control, check out the cli specific :doc:`help page `.