Custom type

Objectives

In this sections we are going to learn how to create custom types with Rougail. In short, a custom type is nothing more than a kind of a template for a variable or a family.

Prerequisites

  • We assume that Rougail’s library is installed on your computer.

  • It is possible to retrieve the current state of the various Rougail files manipulated in this tutorial step by checking out the corresponding tag of the rougail-tutorials git repository. Each tag corresponds to a stage of progress in the tutorial. Of course, you can also decide to copy/paste or download the tutorial files contents while following the tutorial steps.

If you want to follow this tutorial with the help of the corresponding rougail-tutorials git repository, this workshop page corresponds to the tags v1.1_080 to v1.1_085 in the repository.

git clone https://forge.cloud.silique.fr/stove/rougail-tutorials.git
git switch --detach v1.1_080

HTTP Proxy with “proxy” type

Let’s look now at the possibilities for adding types. This feature is essential for having a truly adaptable tool.

Note

It is actually possible to add types, but that involves adding predefined types to the Tiramisu underlying consistency library itself, and that’s a different matter altogether. There is a simpler way to achieve this with the Rougail type definition feature.

It is possible in Rougail to create custom types in a very simple way, it is much like defining variables or families.

Our folder structure will be expanded a bit:

.
├── firefox
│   ├── 00-proxy.yml
│   ├── 10-manual.yml
│   └── 20-manual.yml
└── types
    └── proxy
        └── 00-type.yml


Notice that we have now a new types folder in our tree structure. This is where we will place our new proxy type definition.

Note

Choosing the types/proxy/00-type.yml file name is not mandatory. You can name this folder whatever you want. It is simply a good practice to separate structure files from type definition files. We will see later on how to indicate to Rougail that a file contains a type declaration.

In accordance with our naming policy, we have created a file named 00-type.yml inside the proxy folder. Let’s examine this more closely this types/proxy/00-type.yml type definition file:

The types/proxy/00-type.yml type structure file that defines our new proxy type.
%YAML 1.2
---
version: 1.1

proxy:

  address:
    description: Proxy address
    type: domainname
    params:
      allow_ip: true

  port:
    description: Proxy port
    type: port
    default: 8080
...

The new type named proxy is declared the same way we declare a family. It’s more or less like a family declaration except it will be used as a type definition. If you remember, it’s mostly the content as the family manual.http_proxy in firefox/10-manual.yml file of this tutorial.

How do we use a new type?

Very simply. In the usual way: we will add a type parameter in our family declaration.

Let’s modify the existing family using this new proxy type:

The firefox/10-manual.yml structure file with less code due to the proxy type definition
%YAML 1.2
---
version: 1.1

manual:
  description: Manual proxy configuration
  disabled:
    variable: _.proxy_mode
    when_not: Manual proxy configuration

  http_proxy:
    description: HTTP Proxy
    type: proxy
...

We can see that the type declared for the http_proxy family is now type: proxy. It’s perfectly natural in the Rougail style.

Due to the type definition we can “omit” some settings in the structure file such as the domainname type and the allow_ip parameter. And the address variable it is not necessary to be specified at all in the structure file because it’s in the type definition.

In order to use our new type we need to declare it to Rougail otherwise the new type is not available. The Rougail CLI has the --types type declaration command line option for this purpose. Let’s launch the Rougail CLI with this command line parameter:

rougail -m firefox/ --types types/proxy -u yaml -yf config/01/config.yml

The CLI output is entirely standard and does not specifically mention the call to a new type. The result is the same as usual, as if the type declaration were omitted, which is what we want. We want the same behavior as usual.

╭────────────── Caption ───────────────╮
│ Variable Default value               │
│          Modified value              │
│          (⏳ Original default value) │
╰──────────────────────────────────────╯
Variables:
┣━━ 📓 proxy_mode (Configure Proxy Access to the Internet): Manual proxy 
configuration ◀ loaded from the YAML file "config/01/config.yml" (⏳ No 
proxy)
┗━━ 📂 manual (Manual proxy configuration)
    ┣━━ 📂 http_proxy (HTTP Proxy)
    ┣━━ 📓 address (Proxy address): http.proxy.net ◀ loaded from the YAML 
    file "config/01/config.yml"
    ┗━━ 📓 port (Proxy port): 3128 ◀ loaded from the YAML file 
        "config/01/config.yml" (⏳ 8080)
    ┣━━ 📓 use_for_https (Also use this proxy for HTTPS): false ◀ loaded from 
    the YAML file "config/01/config.yml" (⏳ true)
    ┣━━ 📂 https_proxy (HTTPS Proxy)
    ┣━━ 📓 address (HTTPS address): https.proxy.net ◀ loaded from the YAML 
    file "config/01/config.yml" (⏳ http.proxy.net)
    ┗━━ 📓 port (HTTPS port): 3128
    ┗━━ 📂 socks_proxy (SOCKS Proxy)
        ┣━━ 📓 address (SOCKS address): http.proxy.net
        ┣━━ 📓 port (SOCKS port): 3128
        ┗━━ 📓 version (SOCKS host version used by proxy): v5

HTTPS and SOCKS Proxy with “proxy” type

For those who follow the tutorial with the help of the git repository

Now you need to checkout the v1.1_081 version:

git switch –detach v1.1_081

With this use of a type, it is possible to define our HTTPS and SOCKS Proxy in a much more conscious way: It’s very practical. In fact, the type definition adds nothing else to the structural logic.

The firefox/20-manual.yml structure file with less code due to the proxy type definition
%YAML 1.2
---
version: 1.1

manual:

  use_for_https: true  # Also use this proxy for HTTPS

  https_proxy:
    description: HTTPS Proxy
    type: proxy
    hidden:
      variable: _.use_for_https

  socks_proxy:
    description: SOCKS Proxy
    type: proxy
...

Now we have two families with the proxy type: https_proxy and socks_proxy.

But we haven’t recovered everything we had before. Using a type definition is fine, but we still need to find exactly the configuration situation we had before using the type definition.

Add a variable in a family with custom type

For those who follow the tutorial with the help of the git repository

Now you need to checkout the v1.1_082 version:

git switch --detach v1.1_082

In the socks_proxy family definition, we need to added a new variable, the version variable:

version:
  description: SOCKS host version used by proxy
  choices:
    - v4
    - v5
  default: v5

You can see that what we call a type definition in Rougail is very flexible, because it is perfectly possible to add to what has been defined in the type. The type definition here is therefore not only a template but much more an extensible schema.

Redefine default value in custom type variable

For those who follow the tutorial with the help of the git repository

Now you need to checkout the v1.1_083 version:

git switch --detach v1.1_083

In this section we are going to make an implicit redefinition of variables default values.

redefine

A redefine is a redefinition of all or part of the attributes of a variable as it was previously defined. There are two types of redefine, an implicit and an explicit. An implicit redefinition is a redefinition which has not been declared with the redefine attribute. An explicit redefinition is a redefinition which has been declared with the redefine: true attribute.

Only default values can be implicitly redefined (and only in the type definitions), all other attributes need to be explicitely redefined.

In the type definition file, we are now setting default values for the address and port variables:

The types/proxy/00-type.yml type definition file with the default values for address and port
%YAML 1.2
---
version: 1.1

proxy:

  address:
    description: Proxy address
    type: domainname
    params:
      allow_ip: true
    default:
      variable: __.http_proxy.address

  port:
    description: Proxy port
    type: port
    default:
      variable: __.http_proxy.port
...

But we don’t want theses default values for HTTP definition. We can redefine these type settings in the structure file:

The firefox/10-manual.yml structure definition file with the redefined default values for address and port
%YAML 1.2
---
version: 1.1

manual:
  description: Manual proxy configuration
  disabled:
    variable: _.proxy_mode
    when_not: Manual proxy configuration

  http_proxy:
    description: HTTP Proxy
    type: proxy

    address:
      default: null

    port:
      default: 8080
...

This means that the default values for these variables will now be those defined in the structure file if the values are defined (and not in the type definitions file).

Let’s have a closer look at the behavior here.

Let’s launch the Rougail CLI:

rougail -m firefox/ --types types/proxy -u yaml -yf config/01/config.yml

We have this output:

╭────────────── Caption ───────────────╮
│ Variable Default value               │
│          Modified value              │
│          (⏳ Original default value) │
╰──────────────────────────────────────╯
Variables:
┣━━ 📓 proxy_mode (Configure Proxy Access to the Internet): Manual proxy 
configuration ◀ loaded from the YAML file "config/01/config.yml" (⏳ No 
proxy)
┗━━ 📂 manual (Manual proxy configuration)
    ┣━━ 📂 http_proxy (HTTP Proxy)
    ┣━━ 📓 address (Proxy address): http.proxy.net ◀ loaded from the YAML 
    file "config/01/config.yml"
    ┗━━ 📓 port (Proxy port): 3128 ◀ loaded from the YAML file 
        "config/01/config.yml" (⏳ 8080)
    ┣━━ 📓 use_for_https (Also use this proxy for HTTPS): false ◀ loaded from 
    the YAML file "config/01/config.yml" (⏳ true)
    ┣━━ 📂 https_proxy (HTTPS Proxy)
    ┣━━ 📓 address (Proxy address): https.proxy.net ◀ loaded from the YAML 
    file "config/01/config.yml"
    ┗━━ 📓 port (Proxy port): 8080
    ┗━━ 📂 socks_proxy (SOCKS Proxy)
        ┣━━ 📓 address (Proxy address): socks.proxy.net ◀ loaded from the YAML 
        file "config/01/config.yml"
        ┣━━ 📓 port (Proxy port): 8080
        ┗━━ 📓 version (SOCKS host version used by proxy): v5

We can see in the output that

  • the default values of the port variable, which is 8080 in the structure file, comes from the strucure file.

  • the default values of the address variable is not set because it is null by default in the structure file, so there is not such thing as a default value set for this variable.

What happens if the default value in the type definition is not suitable for us?

If we hadn’t defined a value in the userdata file, the Rougail CLI would have returned the following error:

🛑 Caution
┗━━ Manual proxy configuration
    ┣━━ HTTP Proxy
    ┃   ┗━━ Proxy address: 🛑 mandatory variable but has no value
    ┗━━ SOCKS Proxy
        ┗━━ Proxy address: 🛑 mandatory variable but has no value

Therefore, the default value that was taken into account is indeed that of the structure file, and not that of the type definition.

Note

We can see that the Rougail behavior is always very simple, there is no such thing as an MRO (Method Resolution Object) that try fallback to the default value defined in the type definition file. The Rougail behavior is much more simple and transparent.

kinematics of setting the address value

Let’s take the time to explain how the value of the variable manual.http_proxy.address is determined using the type definition.

The 10-manual.yml structure file defines the manual.http_proxy family which is defined with the help of the proxy type:

http_proxy:
  description: HTTP Proxy
  type: proxy

In the types/proxy/00-type.yml file we have the proxy type definition and its default value setting:

default:
  variable: __.http_proxy.address

This default value setting point to the http_proxy.address value in the firefox/10-manual.yml structure file:

address:
  default: null

Which is null by default but in the config/01/config.yml user data file we have the http.proxy.net value set:

manual:
  http_proxy:
    address: http.proxy.net

In the CLI standard output we can see that the http_proxy.address variable value is finally set by the user data file:

┗━━ 📂 manual (Manual proxy configuration)
    ┣━━ 📂 http_proxy (HTTP Proxy)
    ┃   ┣━━ 📓 address (Proxy address): http.proxy.net ◀ loaded from the YAML
    ┃   ┃   file "config/01/config.yml"

Redefine other parameter in custom type for HTTP

For those who follow the tutorial with the help of the git repository

Now you need to checkout the v1.1_084 version:

git switch --detach v1.1_084

In this sections we are going to make explicit redefinitions.

Note here that, in order to allow for the genericity of a type declaration, we have omitted to specify the variable descriptions in the type declaration. We will therefore need to enter these descriptions in the structure file.

The types/proxy/00-type.yml type definition file with the default definitions and no description
%YAML 1.2
---
version: 1.1

proxy:

  address:
    type: domainname
    params:
      allow_ip: true
    default:
      variable: __.http_proxy.address

  port:
    type: port
    default:
      variable: __.http_proxy.port
...

So, as the address and port variables don’t have any description attribute, we need to add one. It is something like an extension of the type definitions here in the structure file:

The firefox/10-manual.yml structure definition file with explicit redefinitions
%YAML 1.2
---
version: 1.1

manual:
  description: Manual proxy configuration
  disabled:
    variable: _.proxy_mode
    when_not: Manual proxy configuration

  http_proxy:
    description: HTTP Proxy
    type: proxy

    address:
      redefine: true
      default: null
      description: HTTP proxy address

    port:
      redefine: true
      default: 8080
      description: HTTP proxy port
...

Note

Transforming an implicit defintion into an explicit redefinition is very simple; you just need to add the redefine: true attribute:

Let’s launch the Rougail CLI:

rougail -m firefox/ --types types/proxy -u yaml -yf config/01/config.yml

We have this output:

╭────────────── Caption ───────────────╮
│ Variable Default value               │
│          Modified value              │
│          (⏳ Original default value) │
╰──────────────────────────────────────╯
Variables:
┣━━ 📓 proxy_mode (Configure Proxy Access to the Internet): Manual proxy 
configuration ◀ loaded from the YAML file "config/01/config.yml" (⏳ No 
proxy)
┗━━ 📂 manual (Manual proxy configuration)
    ┣━━ 📂 http_proxy (HTTP Proxy)
    ┣━━ 📓 address (Proxy address): http.proxy.net ◀ loaded from the YAML 
    file "config/01/config.yml"
    ┗━━ 📓 port (Proxy port): 3128 ◀ loaded from the YAML file 
        "config/01/config.yml" (⏳ 8080)
    ┣━━ 📓 use_for_https (Also use this proxy for HTTPS): false ◀ loaded from 
    the YAML file "config/01/config.yml" (⏳ true)
    ┣━━ 📂 https_proxy (HTTPS Proxy)
    ┣━━ 📓 address (Proxy address): https.proxy.net ◀ loaded from the YAML 
    file "config/01/config.yml" (⏳ http.proxy.net)
    ┗━━ 📓 port (Proxy port): 3128
    ┗━━ 📂 socks_proxy (SOCKS Proxy)
        ┣━━ 📓 address (Proxy address): http.proxy.net
        ┣━━ 📓 port (Proxy port): 3128
        ┗━━ 📓 version (SOCKS host version used by proxy): v5

We can see in the output that the address and port variables have default values, and descriptions (“HTTP proxy address”, “HTTP proxy port”) that weren’t present in the type definition file.

Redefine other parameter in custom type for HTTPS and SOCKS

For those who follow the tutorial with the help of the git repository

Now you need to checkout the v1.1_085 version:

git switch --detach v1.1_085

We’re going to do exactly the same modification with the socks_proxy family, that is we will base the socks_proxy family on the type definition.

Following the same reasoning, we add a in the structure file some description attributes to the socks_proxy family and also add some explicit redefinition attribute (redefine: true).

The firefox/20-manual.yml structure file with the redefine and the description attribute
%YAML 1.2
---
version: 1.1

manual:

  use_for_https: true  # Also use this proxy for HTTPS

  https_proxy:
    description: HTTPS Proxy
    type: proxy
    hidden:
      variable: _.use_for_https

    address:
      redefine: true
      description: HTTPS proxy address

    port:
      redefine: true
      description: HTTPS proxy port

  socks_proxy:
    description: SOCKS Proxy
    type: proxy

    address:
      redefine: true
      description: SOCKS proxy address

    port:
      redefine: true
      description: SOCKS proxy port

    version:
      description: SOCKS host version used by proxy
      choices:
        - v4
        - v5
      default: v5
...

Key points

  • the very practical aspects of type declaration

  • type usage in the Rougail CLI

  • more conscious family definition

  • using a type for an extended family definition

  • we can redefine things in the structure file and it overloads the type definition file

  • implicit or explicit redefinitions

We have seen how the definition of type, used effectively, allows for a new expressiveness.