Welcome to luci’s documentation!

luci

pypi documentation pipeline status updates coveralls codestyle

What luci is about:

  • you add metadata to any type of text file (most likely, but not necessarily, in it’s comments)
  • you point luci to that file
  • luci reads the metadata
  • luci uses that metadata as configuration to (optionally) create a command-line interface for the file in question, and as configuration for a task of your choosing
  • luci executes that task, letting the user specify additional metadata/configuration values via the dynamically created command-line interface (if applicable)

‘What.. ??’, ‘Those words you just said are weird!!’, ‘English much?!?’ – Yes, sorry. I’m having real trouble coming up with a good and simple explanation for all this. Honestly, I’d be happy to settle for only one of the two!

luci is the result of one of those situations where I needed a solution for an issue I had (and which would be even more difficult to explain), and where I decided I saw an interesting enough pattern worth trying to implement a generic solution for. I know, I know, typical over-engineered piece of crap solution looking for problems. I don’t care though.

To illustrate the class of problems luci can help with, here are two quick and dirty…

Examples

A dynamic text emitter

Say you are developing a shell script that processes text via stdin, and for development purposes (testing regular expressions?) you need to pipe in some (changing, but predictable) text input. This can be achieved quickly in a number of ways, but with luci I think it’s especially straightforward. You create a file (let’s call it dev_input):

Now, make it executable (chmod +x dev_input), and put it somewhere into your $PATH. That’s all, now you’ve got an executable that emits templated text (which can be way more complex than this example, you’d just use some of the more advanced bells and whistles of the jinja2 templating engine) that can be controlled via command-line switches:

Check out the collected examples for more details and options.

Make a file ‘updateable’

Say we want to share the file from the last example with our co-workers, and every now and then we update it to add new ‘features’. Of course, we will check it into a git repository. But checking out that repo might or might not be too much overhead for just one file. We could, instead, add the download url to the file’s metadata, and use a luci task to update it. Here’s how the updated file looks like:

Once our co-worker has this file on their machine, and we publish an update, all they need to do is:

❯ luci download /home/markus/.local/bin/dev_input --replace

Again, this is only a very simplistic example to quickly show what luci is about. Check out the full example (and the luci documentation in general) for more details.

luci comes with a few default ‘commands’ (‘lucifiers’ in luci-speak) like the download one. You can easily write your own lucifiers, and that’s where I see luci’s real value, as that let’s you, fairly quickly, create applications and scripts for sorta ‘secondary’ tasks related to the data you commonly work with. In an organized and reproducible way.

Because of it’s generic nature I think examples are best suited to convey the type of problems luci can help solve. Which is why I wrote up some more usage examples.

Obviously, nothing luci does can’t be achieved by writing your own scripts. I honestly have no idea how people feel about embedding task-specific metadata in their files. I think it’s a neat idea in some cases, even if I find it really hard to tell you why I do :-)

Install

❯ pip install luci

I’ll leave it as exercise for the reader to decide whether to use sudo or create a virtualenv or similar for a tidier installation.

Features

  • generic (to a fault)
  • extensible (plugin-architecture, add your own dictlet-finders, dictlet-readers, and lucifiers)
  • dynamic command-line interface generation
  • includes default task implementations (‘lucifiers’) that help with development/debugging (debug, metadata, cat) as well as common generic use-cases (template, download)

ToDo

  • look into performance
  • add more (input) formats in addition to yaml
  • add a few more generic lucifiers
  • enable the use of non-text files, directories, urls etc.
  • tests

License

  • Free software: GNU General Public License v3

Credits

click
used to dynamically create the dictlet command-line interface
jinja2
does all the templating in the background
stevedore
used to manage plugins
cookiecutter
was used to create the initial project layout

Installation

Stable release

To install luci, run this command in your terminal:

$ pip install luci

This is the preferred method to install luci, as it will always install the most recent stable release.

If you don’t have pip installed, this Python installation guide can guide you through the process.

From sources

The sources for luci can be downloaded from the Github repo.

You can either clone the public repository:

$ git clone git://github.com/makkus/luci

Or download the tarball:

$ curl  -OL https://github.com/makkus/luci/tarball/master

Once you have a copy of the source, you can install it with:

$ python setup.py install

Using inaugurate

inaugurate is a generic bootstrap script that can, among other things, install python packages in it’s own conda or virtualenv (in the users home directory):

Using conda

$ curl https://inaugurate.sh | NO_EXEC=true bash -s -- luci

Using virtualenv

$ curl https://inaugurate.sh | sudo NO_EXEC=true bash -s -- luci

Usage

luci’s command-line syntax is based on sub-commands:

❯ luci [luci-opts] LUCIFIER [lucifier-opts] DICTLET [dictlet-opts]

Currently, [luci-opts] only include the --vars/-v option. It is used to specify additional variables to overwrite dictlet defaults (but which can be overwritten by user-provided input).

lucifiers

Lucifiers are Python classes that describe tasks. Those tasks are configured by the metadata luci parses and assembles. One of the design goals of luci is to make it easy to write and add additional Lucifiers, to find out more about that please follow this link.

lucifier options

lucifiers can be configured with their own set of options. Those are generally very high level, and independent of the dictlet (see below) they use. Usually they would be things like whether you want to use a pager for output, or which format it’s output should be. A lucifiers options can be queried like so:

❯ luci LUCIFIER --help

Default lucifiers

That being said, luci comes with a couple of generic lucifiers which should cover a few main use-cases. If you think there’s an obvious one that’s missing, let me know! Here’s a list of currently available ones:

cat
Overview
Command-line interface

(Autogenerated) cli help

luci cat

Very simple lucifier, outputs the part of the dictlet that is not processed by luci.

luci cat [OPTIONS] DICTLET

Options

-l, --luci-content

print the whole dictlet, including luci metadata

-p, --pager

whether to use a pager for display

Metadata requirements
Examples
debug
Overview
Command-line interface

(Autogenerated) cli help

luci debug

Lucifier to be used when debugging dictlets.

It let’s you print debug information of both metadata and content of a dictlet to stdout.
luci debug [OPTIONS] DICTLET

Options

-l, --all-lines

display all line-related information (‘content’, ‘content_filtered’, ‘metadata’)

-la, --lines-all

display all lines of a template

-mu, --metadata-user

display user-input arguments

-lm, --lines-metadata

display all lines of a template that were processed by luci

-a, --all

display all available information

-ma, --all-metadata

display all available metadata information

-md, --metadata-defaults

display default vars

-lc, --lines-content

display all lines of a template that were skipped by luci processing and will be used as content

-mt, --metadata-dictlet

display processed dictlet metadata

-ml, --metadata-luci

display luci-related metadata

-f, --format <__dictlet__.output_type>

the output type

-m, --metadata

display the merged metadata)

-p, --pager

whether to use a pager for display

-lf, --lines-filtered

display all lines of a template that were skipped by luci processing and are pre-filtered

Metadata requirements
Examples
download
Overview
Command-line interface

(Autogenerated) cli help

luci download

Lucifier that can update a template by downloading and replacing it on filesystem.

The dictlet needs to contain a metadata key ‘url’ with a child key ‘default’ and the remote url to download as value. It can also, optionally, contain a child key ‘version’ and a templated string like ‘https://dl.com/dictlet-{{ version }}.yml’ as value. The latter will be used if the ‘–version’ option is specified (as described below).

This lucifier adds a few command-line options to the dictlet-part of the command (not the lucifier part, as is normal – mostly because I like it better that way in this case):

  • target (‘–target’, ‘-t’): a target file
  • replace (‘–replace’, ‘-r’): whether to replace an existing file
  • version (‘–version): whether to download a specific version of the file

By default, this lucifier will print out the content of the remote file. If the ‘–replace’ option is specified, the template itself will be replaced with the remote file. If the ‘–target’ option is specified, the remote file will be downloaded to it’s value instead.

luci download [OPTIONS] DICTLET

Options

-p, --pager

whether to use a pager for display

Metadata requirements
Examples
metadata
Overview
command-line interface

(Autogenerated) cli help

luci metadata

Lucifier that extracts metadata out of a dictlet and prints it to stdout.

Args:
luci_metadata (bool): whether to also display ‘luci’-specific metadata output_type (str): the format of the output. Available: ‘raw’, ‘json’, ‘yaml’ pager (bool): whether to use a pager or not
luci metadata [OPTIONS] DICTLET

Options

-l, --luci-metadata

whether to print internal metadata (mostly useful for debugging purposes)

-f, --format <__dictlet__.output_type>

the output type

-p, --pager

whether to use a pager for display

Metadata requirements
Examples
template
Overview
Command-line interface

(Autogenerated) cli help

luci template

Lucifier that uses the dictlet metadata as variables to replace strings in the dictlet content.

luci template [OPTIONS] DICTLET

Options

-p, --pager

whether to use a pager for display

-f, --format <__user__.output_type>

the output type

-a, --all

whether to include parts that were being used by luci to extract metadata in the output

Metadata requirements
Examples

Custom lucifiers

luci supports custom build, specialized lucifiers. For those to be picked up, the Python packages they come with need to be installed in the current PYTHONPATH (for example, the same virtualenv) and be registered with a name in the ‘luci.lucifiers’ namespace (using the stevedore plugin system). For more details on how this works, please check out the lucifier module documentation.

dictlets

Dictlet is the name I chose to describe a file that contains metadata luci can parse. Dictlets are text files of any sort (bash scripts, yaml files, etc.) that, usually as comments, contain one or several dictionaries in a format luci has a dictlet reader for. In most cases, you can think of a dictlet as a script within a script, that can be ‘activated’ by luci. A template can have it’s own set of command-line arguments which can be either inherited by the lucifier that is used to run it, or which are contained within the metadata of the dictlet. More information on the format of this metadata can be found here, but as a quick example, here’s how it looks to create an option that uses the --name flag, which accepts a string as input, and which stores the user input in the name key, which is a child of the person (root) key:

vars:
  person.name:
    meta:
      alias: 'name'
      type: str

Those command-line options can be described in more detail, if necessary. To query the options of a dictlet, you issue:

❯ luci LUCIFIER DICTLET --help

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

‘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 dictlet readers to extract relevant metadata. The only implemented reader at the moment is the YamlDictletReader, which inherits most of it’s logic from the base class 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 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:

# 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:

❯ 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:

# 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:

# 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:

#! /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 section below and/or the relevant 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:

# 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:

❯ 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:

❯ 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:

❯ luci debug -mu debug_example --number 12

metadata: user vars:

__user_input__:
  arg1: 12

Command-line interface basics

Getting a dictlet (called minimal_arg) to create a command-line interface can be as easy as adding:

# __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:

❯ 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:

❯ luci metadata minimal_arg --arg1 value1
arg1: value1

Now we can make one of the arguments mandatory, like so:

# __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:

❯ 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:

# __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:

❯ luci metadata minimal_arg --arg2 value2
arg1: VALUE_1
arg2: value2

If you need more fine-grained control, check out the cli specific help page.

Examples

As luci is designed to help with a fairly generic (and weird) pattern, I find it quite hard to describe what problems it can help you with. I figured, showcasing examples might make more sense. Even if – as you’ll probably discover – some of those examples might seem a bit far-fetched…

Metadata

Extract metadata from any textfile

(…as long as the file’s format allows for comments)

Say, you’ve got a bunch of csv files that you use for your research project. Now, the all contain ‘raw’ data, but you might want to ‘tag’ them with certain metadata fields to be able to process them in groups, or for whatever else reason. You can of course use the filename and a certain folder structure to organize them (which you should do in either case). But that can be fairly limited if there are overlapping categories.

So, why not add metadata directly into the data files (provided the file-format you use allows for comments or similar)? In that case luci can help you extract it. Of course, you could do that anyway and write your own script to get the metadata out. But why would you, now that luci exists, right?

Here’s how the csv file (let’s call it data.csv) could look (to be honest, I have no idea how to add comments to csv files, but let’s just assume we can use ‘#’ line-prefixes):

# lucify: start
# author: Markus Binsteiner
# date: "2018-03-03"
# tags:
#   - luci
#   - example

date,subject,result
2018-01-01,alice,okish
2018-01-02,tom,gone crazy

And here’s how to get the metadata out, using ‘json’ as output format:

❯ luci metadata --format json data.csv
{
    "author": "Markus Binsteiner",
    "date": "2018-03-03",
    "tags": [
        "luci",
        "example"
    ]
}

You could use that output in a pipeline, possibly with jq_ or in a script.

Templating

Super-basic example

Say, we have a file containing:

# __luci__:
#   vars:
#     - he
#     - she
What he said: {{ he }}
Then, she said: {{ she }}

Save that to a file named ‘who_said_what’. Users of luci can query this dictlet (this is what we call files that are consumed by luci) now for it’s interface:

❯ luci template who_said_what --help
Usage: luci template who_said_what [OPTIONS]

Options:
  --he TEXT   n/a
  --she TEXT  n/a
  --help      Show this message and exit.

And, to do the actual templating, we do something like:

❯ luci template who_said_what --he "I don't care" --she "I don't care either"

What he said: I don't care
Then, what she said then: I don't care either

This of course is not really useful, but those dictlets can become much more complex (using loops, conditional statements, etc.), as luci uses the powerful jinja2 as it’s templating engine.

Using default value for user arg

In the previous example we used the most basic user input options, 2 strings. luci supports much more specific options though. For example, say we want to add an optional name for the 2nd person saying stuff. We’d change the dictlet like so:

# __luci__:
#   vars:
#     - he
#     - she
#     - name:
#         default: she
What he said: {{ he }}
Then, {{ name }} said: {{ she }}

Now, the --help options gives us:

❯ luci template who_said_what --help
Usage: luci template who_said_what_name
           [OPTIONS]

Options:
  --he TEXT    n/a
  --she TEXT   n/a
  --name TEXT
  --help       Show this message and exit.

And we can run it like:

❯ luci template who_said_what --he "Wha?" --she "Ya!" --name "Lola"
What he said: Wha?
Then, Lola said: Ya!

As luci uses the click Python library to dynamically create command-line linterfaces, we can use all of Click’s option features to control the created interface. More details on how to create a cli with luci can be found here.

Add boilerplate to a project

When developing, often you need to add boilerplate code to a file or project, with just a few strings different between each instance. Perfect use-case for templating. Let’s use luci itself as an example. luci uses what is called a lucifier to implement different types of tasks that use dictlet metadata and content as input. Those all inherit from a base class, and need to contain a few attributes and functions. We can easily use a template, combined with user input, to either attach source code describing such a class to a file (or just create a new file). Here’s how that particular ‘add-lucifierdictlet looks like:

#! /usr/bin/env luci
#
# __lucify__:
#   __default_lucifier__: template
#   vars:
#     lucifier.name:
#        meta:
#          type: str
#          required: true
#        doc:
#          help: the name of the lucifier (without the 'Lucifier'-postfix)
#        cli:
#          option:
#            nargs: 1
#            required: true
#            param_decls: ["--name", "-n"]
#     lucifier.help:
#         meta:
#           type: str
#           required: false
#           alias: lucifier-help
#         doc:
#           help: help string for this lucifier
#     lucifier.short_help:
#         meta:
#           type: str
#           required: false
#           alias: lucifier-short-help
#         doc:
#           help: short help string for this lucifier
#     lucifier.epilog:
#         meta:
#           type: str
#           required: false
#           alias: lucifier-epilog
#         doc:
#           help: epilog string for this lucifier
#     lucifier.var_names:
#         meta:
#           type: list
#           required: false
#         doc:
#           help: names of cli variable names
#         cli:
#           option:
#             param_decls: ["--var-names", "-v"]
#             multiple: true
#             type: str
class {{ lucifier.name | capitalize }}Lucifer(Lucifier):
    """{{ lucifier.help }}
    """

    LUCI_{{ lucifier.name | upper }} = {
        "doc": {
            "short_help": "{{ lucifier.short_help }}",
            # "epilog": "{{ lucifier.epilog }}"
        },
        "vars": {
            {% for var_name in lucifier.var_names %}"__{{ lucifier.name | lower }}__.{{ var_name }}": {
                 "type": str,
                 "required": False,
                 "default": False,
                 "doc": {
                     "help": "<var help for {{ var_name }}>"
                 },
                 "click": {
                     # check http://click.pocoo.org/6/api/#parameters for details and available keys
                     "option": {
                         "param_decls": ["--{{ var_name }}"],
                         "type": str,
                         # potential other options ...
                     },
                     # "argument": {
                     #    "param_decls": '{{ var_name }}',
                     #    "nargs": 1
                     }
                 }
            },
            {% endfor -%}
            # if you want the following two parameters, you need a: from luci import utils somewhere in this file
            # "pager": utils.create_pager_var(),
            # "output_type": lucifiers.create_output_type_var()
        }
    }

    def __init__(self, **kwargs):

        super({{ lucifier.name | capitalize }}, self). __init__(**kwargs)

    def get_default_metadata(self):

        return {{ lucifier.name | capitalize }}Lucifier.LUCI_{{ lucifier.name | upper }}

    def process_dictlet(self, metadata, dictlet_details):

        utils.output(metadata, format="raw", pager=False)

Note how we use click - specific parameters param_decls and multiple for the lucifier.var_names variable:

#     lucifier.var_names:
#         meta:
#           type: list
#           required: false
#         doc:
#           help: names of cli variable names
#         cli:
#           option:
#             param_decls: ["--var-names", "-v"]
#             multiple: true
#             type: str

To output the assembled boilerplate template to stdout, all we need to do is:

❯ luci template add-lucifier --help
Usage: luci template add-lucifier [OPTIONS]

Options:
  -n, --name TEXT             the name of the lucifier (without the
                              'Lucifier'-postfix)  [required]
  --lucifier-help TEXT        help string for this lucifier
  --lucifier-short-help TEXT  short help string for this lucifier
  --lucifier-epilog TEXT      epilog string for this lucifier
  -v, --var-names TEXT        names of cli variable names
  --help                      Show this message and exit.

❯ luci template add-lucifier --name example --lucifier-short-help "An example lucifier" --lucifier-help "An example lucifier" -v input_var_1 -v input_var_2

class ExampleLucifer(Lucifier):
    """An example lucifier
    """

    LUCI_EXAMPLE = {
        "doc": {
            "short_help": "An example lucifier",
            # "epilog": ""
        },
        "vars": {
            "__example__.input_var_1": {
                 "type": str,
                 "required": False,
                 "default": False,
                 "doc": {
                     "help": "<var help for input_var_1>"
                 },
 ...
 ... <more boilerplate>
 ...

Download

Include update information into a shell script

Say, we wrote a cool bash-script we are really proud of:

#!/usr/bin/bash
#
# Copyright Markus Binsteiner, 2018
#                       version 0.1

echo "Awesome!"

Now, somebody found a bug in the script. So, we fix that bug, and we release a new version. How do we get that new version to our users easily, given that it’s only a shell script, not packaged. And a git checkout of a whole repository doesn’t make all that much sense for only one file either?

With luci, we can add the remote url of the script in a commented section somewhere, like:

#!/usr/bin/env bash
#
# lucify: start
#
# url:
#   default: https://raw.githubusercontent.com/makkus/luci-examples/master/download/awesome/awesome.sh
#   version: https://raw.githubusercontent.com/makkus/luci-examples/{{ version }}/download/awesome/awesome.sh
#
# lucify: stop
#
# Copyright Markus Binsteiner, 2018
#                       version 0.2

echo "Super awesome!"

Then, if one wanted to update the script itself, they’d issue:

❯ luci download awesome.sh --replace

Downloading: https://raw.githubusercontent.com/makkus/luci-examples/master/download/awesome/awesome.sh
Saving file: /home/markus/.local/bin/awesome.sh
Done.

The --replace option is necessary, as otherwise the download command would only print out the content of the remote file. To see all the available options for a particular task, we can use the --help flag:

❯ luci download awesome.sh --help
Usage: luci download awesome.sh [OPTIONS]

Options:
  -r, --replace      if set, the target file will be overwritten
  -t, --target PATH  if specified, the url content will be saved into the specified file,
                     otherwise the dictlet itself will be replaced
  --version VERSION  the version of the dictlet to download (optional)
  --help             Show this message and exit.

Of course, this example only makes limited sense, as we’d have to ask our users to install a Python package (luci) just to update a single bash script. Let’s ignore that though, shall we?

Scripting

Executable dictlets

You can make every dictlet (that is not a script by itself in the first place) executable by setting luci as it’s interpreter in the she-bang line, and adding the __default_lucifier__ key in the __luci__ metadata:

#! /usr/bin/env luci
#
# __luci__:
#   __default_lucifier__: template
#   vars:
#     - he
#     - she
What he said: {{ he }}
Then, what she said: {{ she }}

Make this file executable (chmod +x who_said_what), put it somewhere in your $PATH and you’ve got a dynamic text emitter script:

❯ who_said_what --help
Usage: luci dev_input [OPTIONS]

Options:
  --he TEXT   n/a
  --she TEXT  n/a
  --help      Show this message and exit.

❯ who_said_what --he Right --she Ok
What he said: Right
Then, what she said: Ok

Authors

Written by

Contributors

None yet. Why not be the first?

History

0.1.1 (2018-04-05)

  • Refactored basically everything
  • Much more documentation

0.1.0 (2018-03-15)

  • First release on PyPI.

Indices and tables