Compare commits
75 commits
main
...
docs_updat
Author | SHA1 | Date | |
---|---|---|---|
![]() |
91ffd19336 | ||
![]() |
fce54f7c84 | ||
![]() |
cfb6017f32 | ||
![]() |
a932d7e1c3 | ||
![]() |
2da85eb0e8 | ||
![]() |
9ca27d7f51 | ||
![]() |
2d39fe8bad | ||
![]() |
10a25b0cfe | ||
![]() |
0f0d955e9f | ||
![]() |
038cd9429a | ||
![]() |
cc54cf675f | ||
![]() |
b4efbf1d71 | ||
![]() |
28fe83cfb2 | ||
![]() |
6edda03d79 | ||
![]() |
55bda5cd8c | ||
![]() |
543d54144b | ||
![]() |
98f788ee9b | ||
![]() |
bf6120f95e | ||
![]() |
04f30963f4 | ||
![]() |
eda0632809 | ||
![]() |
c2111fe581 | ||
![]() |
9870d949b9 | ||
![]() |
5dc7d2b630 | ||
![]() |
fbc0744f61 | ||
![]() |
d7b2e9b521 | ||
![]() |
5cf4fb16a4 | ||
![]() |
a0546a137f | ||
![]() |
b1ed2d3aaf | ||
![]() |
39f01b36a1 | ||
![]() |
c361629b63 | ||
![]() |
45b9a5495e | ||
![]() |
19ecc56316 | ||
![]() |
0ce1550028 | ||
![]() |
cd850f1089 | ||
![]() |
a310b6f063 | ||
![]() |
50299203af | ||
![]() |
080bb9c489 | ||
![]() |
d47011efe4 | ||
![]() |
9a84e28765 | ||
![]() |
c244a816f3 | ||
![]() |
9199631ea5 | ||
2d02da9939 | |||
06bb8f8fad | |||
e2eb58664a | |||
2c0763c516 | |||
ccb4eff30b | |||
7bdc39e370 | |||
ea93bc55fc | |||
ec86795768 | |||
810d08822b | |||
83840e329c | |||
1389929371 | |||
b21c13fea7 | |||
a827ca2225 | |||
75778cb39e | |||
9e614eb7a4 | |||
90ef65dd40 | |||
![]() |
fd5b702686 | ||
d4167b1586 | |||
161985ab50 | |||
908356495b | |||
eb561f5f52 | |||
539ecc7412 | |||
![]() |
b39b728a90 | ||
![]() |
80e3b73785 | ||
![]() |
a7c862cccd | ||
e2d62211ec | |||
9705830e88 | |||
0a9d1b3014 | |||
![]() |
4f33b7c7af | ||
bdd56d31c8 | |||
![]() |
6f9a981da7 | ||
![]() |
4c6f451045 | ||
![]() |
fc8c1ecfda | ||
![]() |
7f7fe5f08f |
47
.pre-commit-config.yaml
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
repos:
|
||||||
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
|
rev: v2.3.0
|
||||||
|
hooks:
|
||||||
|
- id: check-yaml
|
||||||
|
- id: end-of-file-fixer
|
||||||
|
- id: trailing-whitespace
|
||||||
|
- id: check-ast
|
||||||
|
- id: check-added-large-files
|
||||||
|
- id: check-json
|
||||||
|
- id: check-executables-have-shebangs
|
||||||
|
- id: check-symlinks
|
||||||
|
|
||||||
|
- repo: https://github.com/psf/black
|
||||||
|
rev: 22.10.0
|
||||||
|
hooks:
|
||||||
|
- id: black
|
||||||
|
|
||||||
|
# - repo: https://github.com/hhatto/autopep8
|
||||||
|
# rev: v2.0.4
|
||||||
|
# hooks:
|
||||||
|
# - id: autopep8
|
||||||
|
|
||||||
|
# - repo: https://github.com/pre-commit/mirrors-mypy
|
||||||
|
# rev: v1.6.1
|
||||||
|
# hooks:
|
||||||
|
# - id: mypy
|
||||||
|
|
||||||
|
# - repo: https://github.com/PyCQA/pylint
|
||||||
|
# rev: v3.0.2
|
||||||
|
# hooks:
|
||||||
|
# - id: pylint
|
||||||
|
|
||||||
|
# - repo: https://github.com/PyCQA/isort
|
||||||
|
# rev: 5.11.5
|
||||||
|
# hooks:
|
||||||
|
# - id: isort
|
||||||
|
|
||||||
|
# - repo: https://github.com/motet-a/jinjalint
|
||||||
|
# rev: 0.5
|
||||||
|
# hooks:
|
||||||
|
# - id: jinjalint
|
||||||
|
|
||||||
|
# - repo: https://github.com/rstcheck/rstcheck
|
||||||
|
# rev: v6.2.0
|
||||||
|
# hooks:
|
||||||
|
# - id: rstcheck
|
2
docs/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
.venv/
|
||||||
|
build/
|
21
docs/Makefile
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
# Minimal makefile for Sphinx documentation
|
||||||
|
#
|
||||||
|
|
||||||
|
# You can set these variables from the command line, and also
|
||||||
|
# from the environment for the first two.
|
||||||
|
SPHINXOPTS ?=
|
||||||
|
SPHINXBUILD ?= sphinx-build
|
||||||
|
SOURCEDIR = .
|
||||||
|
BUILDDIR = build
|
||||||
|
|
||||||
|
# Put it first so that "make" without argument is like "make help".
|
||||||
|
help:
|
||||||
|
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||||
|
|
||||||
|
.PHONY: help Makefile
|
||||||
|
|
||||||
|
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||||
|
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||||
|
%: Makefile
|
||||||
|
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||||
|
./mistergwen.sh
|
73
docs/_static/terminal.css
vendored
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
.terminal {
|
||||||
|
background-color: #000000; /* Fond noir */
|
||||||
|
color: #00ff00; /* Texte vert, typique des anciens terminaux */
|
||||||
|
font-family: 'Courier New', Courier, monospace; /* Police à chasse fixe */
|
||||||
|
padding: 5px; /* Espace réduit autour du texte */
|
||||||
|
border-radius: 5px;
|
||||||
|
border: 1px solid #00ff00; /* Bordure verte */
|
||||||
|
white-space: pre-wrap;
|
||||||
|
overflow-x: auto; /* Défilement horizontal si nécessaire */
|
||||||
|
box-shadow: 0 0 10px rgba(0, 255, 0, 0.5); /* Ombre verte pour un effet rétro */
|
||||||
|
display: flex; /* Active Flexbox */
|
||||||
|
line-height: 1.2; /* Espacement entre les lignes */
|
||||||
|
display: inline-block; /* Pour que le fond s'adapte au contenu */
|
||||||
|
width: 100%; /* Largeur maximale */
|
||||||
|
}
|
||||||
|
|
||||||
|
.terminal,
|
||||||
|
.terminal * {
|
||||||
|
border: none !important; /* Supprime toutes les bordures à l'intérieur du terminal */
|
||||||
|
}
|
||||||
|
|
||||||
|
.terminal .highlight {
|
||||||
|
margin: 0; /* Supprime les marges */
|
||||||
|
padding: 0; /* Supprime les paddings */
|
||||||
|
background-color: transparent; /* Fond transparent pour éviter les conflits */
|
||||||
|
}
|
||||||
|
|
||||||
|
.terminal .highlight pre {
|
||||||
|
margin: 0; /* Supprime les marges */
|
||||||
|
padding: 0; /* Supprime les paddings */
|
||||||
|
background-color: transparent; /* Fond transparent */
|
||||||
|
color: inherit; /* Hérite la couleur du texte du parent */
|
||||||
|
font-family: inherit; /* Hérite la police du parent */
|
||||||
|
line-height: inherit; /* Hérite l'espacement des lignes du parent */
|
||||||
|
}
|
||||||
|
|
||||||
|
.terminal .highlight pre span {
|
||||||
|
display: inline; /* Évite les espaces inutiles causés par inline-block */
|
||||||
|
margin: 0; /* Supprime les marges */
|
||||||
|
padding: 0; /* Supprime les paddings */
|
||||||
|
}
|
||||||
|
|
||||||
|
.terminal .go {
|
||||||
|
color: #00ff00; /* Couleur pour le texte de sortie du terminal */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* raw html output css */
|
||||||
|
|
||||||
|
/* Styles communs pour les deux classes */
|
||||||
|
.error-box, .output {
|
||||||
|
padding: 10px; /* Espace intérieur */
|
||||||
|
font-size: 0.8em; /* Taille de police plus petite */
|
||||||
|
width: fit-content; /* Ajuste la largeur au contenu */
|
||||||
|
border-radius: 5px; /* Coins arrondis */
|
||||||
|
display: inline-block; /* Pour que la boîte s'ajuste au contenu */
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-box pre, .output pre {
|
||||||
|
margin: 0; /* Supprime la marge par défaut du <pre> */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Styles spécifiques à la classe error-box */
|
||||||
|
.error-box {
|
||||||
|
border: 2px solid #ff0000; /* Bordure rouge */
|
||||||
|
background-color: #ffe6e6; /* Fond légèrement rouge */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Styles spécifiques à la classe output */
|
||||||
|
.output {
|
||||||
|
border: 2px solid #00ff00; /* Bordure verte */
|
||||||
|
background-color: #e6ffe6; /* Fond légèrement vert */
|
||||||
|
}
|
|
@ -1,2 +0,0 @@
|
||||||
variable
|
|
||||||
<https://en.wikipedia.org/wiki/Variable_(computer_science)>`_
|
|
|
@ -117,7 +117,7 @@ Here is a simple example of validating values:
|
||||||
.. code-block:: yaml
|
.. code-block:: yaml
|
||||||
|
|
||||||
---
|
---
|
||||||
version: '1.0'
|
version: '1.1'
|
||||||
my_variable:
|
my_variable:
|
||||||
validators:
|
validators:
|
||||||
- type: jinja
|
- type: jinja
|
||||||
|
@ -142,7 +142,7 @@ In the constraint, it is possible to specify the error level and put it as a war
|
||||||
.. code-block:: yaml
|
.. code-block:: yaml
|
||||||
|
|
||||||
---
|
---
|
||||||
version: '1.0'
|
version: '1.1'
|
||||||
my_variable:
|
my_variable:
|
||||||
validators:
|
validators:
|
||||||
- type: jinja
|
- type: jinja
|
||||||
|
@ -161,7 +161,7 @@ Verification with parameters
|
||||||
.. code-block:: yaml
|
.. code-block:: yaml
|
||||||
|
|
||||||
---
|
---
|
||||||
version: '1.0'
|
version: '1.1'
|
||||||
my_hidden_variable:
|
my_hidden_variable:
|
||||||
disabled: true
|
disabled: true
|
||||||
my_variable:
|
my_variable:
|
||||||
|
@ -189,7 +189,7 @@ An example with a suffix type parameter:
|
||||||
.. code-block:: yaml
|
.. code-block:: yaml
|
||||||
|
|
||||||
---
|
---
|
||||||
version: '1.0'
|
version: '1.1'
|
||||||
varname:
|
varname:
|
||||||
multi: true
|
multi: true
|
||||||
default:
|
default:
|
||||||
|
@ -221,7 +221,7 @@ An example with an index type parameter:
|
||||||
.. code-block:: yaml
|
.. code-block:: yaml
|
||||||
|
|
||||||
---
|
---
|
||||||
version: '1.0'
|
version: '1.1'
|
||||||
family:
|
family:
|
||||||
type: leadership
|
type: leadership
|
||||||
leader:
|
leader:
|
||||||
|
@ -249,7 +249,7 @@ In a first dictionary, let's declare our variable and its verification function:
|
||||||
.. code-block:: yaml
|
.. code-block:: yaml
|
||||||
|
|
||||||
---
|
---
|
||||||
version: '1.0'
|
version: '1.1'
|
||||||
my_variable:
|
my_variable:
|
||||||
validators:
|
validators:
|
||||||
- type: jinja
|
- type: jinja
|
||||||
|
@ -263,7 +263,7 @@ In a second dictionary it is possible to redefine the calculation:
|
||||||
.. code-block:: yaml
|
.. code-block:: yaml
|
||||||
|
|
||||||
---
|
---
|
||||||
version: '1.0'
|
version: '1.1'
|
||||||
my_variable:
|
my_variable:
|
||||||
redefine: true
|
redefine: true
|
||||||
validators:
|
validators:
|
||||||
|
@ -280,7 +280,7 @@ Here is a third dictionary in which we remove the validation:
|
||||||
.. code-block:: yaml
|
.. code-block:: yaml
|
||||||
|
|
||||||
---
|
---
|
||||||
version: '1.0'
|
version: '1.1'
|
||||||
my_variable:
|
my_variable:
|
||||||
redefine: true
|
redefine: true
|
||||||
validators:
|
validators:
|
||||||
|
|
|
@ -177,7 +177,7 @@ It is possible to write the condition in Jinja:
|
||||||
.. code-block:: yaml
|
.. code-block:: yaml
|
||||||
|
|
||||||
---
|
---
|
||||||
version: '1.0'
|
version: '1.1'
|
||||||
condition:
|
condition:
|
||||||
default: 'do not hide!'
|
default: 'do not hide!'
|
||||||
my_variable:
|
my_variable:
|
||||||
|
@ -213,7 +213,7 @@ A variable can therefore be calculated via the result of another variable. Pleas
|
||||||
.. code-block:: yaml
|
.. code-block:: yaml
|
||||||
|
|
||||||
---
|
---
|
||||||
version: '1.0'
|
version: '1.1'
|
||||||
condition:
|
condition:
|
||||||
type: boolean
|
type: boolean
|
||||||
my_variable:
|
my_variable:
|
||||||
|
@ -233,7 +233,7 @@ To delete the calculation from a variable, simply do in a new dictionary:
|
||||||
.. code-block:: yaml
|
.. code-block:: yaml
|
||||||
|
|
||||||
---
|
---
|
||||||
version: '1.0'
|
version: '1.1'
|
||||||
my_variable:
|
my_variable:
|
||||||
redefine: true
|
redefine: true
|
||||||
hidden:
|
hidden:
|
||||||
|
|
116
docs/conf.py
|
@ -1,24 +1,20 @@
|
||||||
# Configuration file for the Sphinx documentation builder.
|
# Configuration file for the Sphinx documentation builder.
|
||||||
#
|
#
|
||||||
# This file does only contain a selection of the most common options. For a
|
|
||||||
# full list see the documentation:
|
|
||||||
# http://www.sphinx-doc.org/en/master/config
|
|
||||||
|
|
||||||
# -- Path setup --------------------------------------------------------------
|
# -- Path setup --------------------------------------------------------------
|
||||||
|
|
||||||
# If extensions (or modules to document with autodoc) are in another directory,
|
import sys, os
|
||||||
# add these directories to sys.path here. If the directory is relative to the
|
|
||||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
|
||||||
#
|
|
||||||
# import os
|
|
||||||
# import sys
|
|
||||||
# sys.path.insert(0, os.path.abspath('.'))
|
# sys.path.insert(0, os.path.abspath('.'))
|
||||||
|
#sys.path.append(os.path.abspath('ext'))
|
||||||
|
sys.path.append('.')
|
||||||
|
|
||||||
|
#---- debug mode ----
|
||||||
|
# shows/hides the todos
|
||||||
|
todo_include_todos = True
|
||||||
|
|
||||||
# -- Project information -----------------------------------------------------
|
# -- Project information -----------------------------------------------------
|
||||||
|
|
||||||
project = 'Rougail'
|
project = 'Rougail'
|
||||||
copyright = '2019-2023, Silique'
|
copyright = '2019-2024, Silique'
|
||||||
author = 'gwen'
|
author = 'gwen'
|
||||||
|
|
||||||
# The short X.Y version
|
# The short X.Y version
|
||||||
|
@ -33,55 +29,46 @@ release = '1.0'
|
||||||
#
|
#
|
||||||
# needs_sphinx = '1.0'
|
# needs_sphinx = '1.0'
|
||||||
|
|
||||||
# Add any Sphinx extension module names here, as strings. They can be
|
|
||||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
|
||||||
# ones.
|
|
||||||
|
|
||||||
extensions = [
|
extensions = [
|
||||||
'sphinx.ext.extlinks', 'sphinx_lesson',
|
'sphinx.ext.extlinks', 'sphinx_lesson', 'sphinx.ext.todo',
|
||||||
#'myst_parser', 'sphinx.ext.extlinks'
|
'sphinx.ext.extlinks', 'ext.xref', 'ext.extinclude'
|
||||||
]
|
]
|
||||||
#
|
|
||||||
#myst_enable_extensions = [
|
|
||||||
# "amsmath",
|
|
||||||
# "attrs_inline",
|
|
||||||
# "colon_fence",
|
|
||||||
# "deflist",
|
|
||||||
# "dollarmath",
|
|
||||||
# "fieldlist",
|
|
||||||
# "html_admonition",
|
|
||||||
# "html_image",
|
|
||||||
## "linkify",
|
|
||||||
# "replacements",
|
|
||||||
# "smartquotes",
|
|
||||||
# "strikethrough",
|
|
||||||
# "substitution",
|
|
||||||
# "tasklist",
|
|
||||||
#]
|
|
||||||
|
|
||||||
|
#---- xref links ----
|
||||||
|
#import the xref.py extension
|
||||||
|
xref_links = {"link_name" : ("user text", "url")}
|
||||||
|
|
||||||
|
#link_name = "Sphinx External Links"
|
||||||
|
#user_text = "modified External Links Extension"
|
||||||
|
#url = "http://www.sphinx-doc.org/en/stable/ext/extlinks.html"
|
||||||
|
#enables syntax like:
|
||||||
|
" :xref:`tiramisu` "
|
||||||
|
links = {
|
||||||
|
'tiramisu': ('Tiramisu', 'https://tiramisu.readthedocs.io/en/latest/'),
|
||||||
|
'tiramisu library': ('Tiramisu library homepage', 'https://forge.cloud.silique.fr/stove/tiramisu'),
|
||||||
|
}
|
||||||
|
xref_links.update(links)
|
||||||
|
#---- ext links ----
|
||||||
# **extlinks** 'sphinx.ext.extlinks',
|
# **extlinks** 'sphinx.ext.extlinks',
|
||||||
# enables syntax like :proxy:`my source <hello>` in the src files
|
# enables syntax like
|
||||||
extlinks = {'proxy': ('/proxy/%s.html',
|
" :source:`v1.1_010/firefox/00-proxy.yml` "
|
||||||
'external link: ')}
|
extlinks = {'source': ('https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/%s',
|
||||||
|
'source: %s'),
|
||||||
|
'tiramisu': ('https://tiramisu.readthedocs.io/en/latest/%s', 'tiramisu: %s'),
|
||||||
|
}
|
||||||
|
#---- options for HTML output ----
|
||||||
default_role = "code"
|
default_role = "code"
|
||||||
|
|
||||||
html_theme = "sphinx_rtd_theme"
|
html_theme = "sphinx_rtd_theme"
|
||||||
|
|
||||||
pygments_style = 'sphinx'
|
pygments_style = 'sphinx'
|
||||||
|
|
||||||
html_short_title = "Rougail"
|
html_short_title = "Rougail"
|
||||||
html_title = "Rougail documenation"
|
html_title = "Rougail documenation"
|
||||||
|
|
||||||
# If true, links to the reST sources are added to the pages.
|
|
||||||
html_show_sourcelink = False
|
html_show_sourcelink = False
|
||||||
|
|
||||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
|
||||||
html_show_sphinx = False
|
html_show_sphinx = False
|
||||||
|
|
||||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
|
||||||
html_show_copyright = True
|
html_show_copyright = True
|
||||||
|
# The name of the Pygments (syntax highlighting) style to use.
|
||||||
|
pygments_style = None
|
||||||
|
html_static_path = ['_static']
|
||||||
|
html_css_files = ['terminal.css']
|
||||||
|
|
||||||
# Add any paths that contain templates here, relative to this directory.
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
templates_path = ['_templates']
|
templates_path = ['_templates']
|
||||||
|
@ -109,41 +96,8 @@ language = 'en'
|
||||||
# List of patterns, relative to source directory, that match files and
|
# List of patterns, relative to source directory, that match files and
|
||||||
# directories to ignore when looking for source files.
|
# directories to ignore when looking for source files.
|
||||||
# This pattern also affects html_static_path and html_extra_path.
|
# This pattern also affects html_static_path and html_extra_path.
|
||||||
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
exclude_patterns = ['.venv', 'build', '_build', 'Thumbs.db', '.DS_Store']
|
||||||
|
|
||||||
# The name of the Pygments (syntax highlighting) style to use.
|
|
||||||
pygments_style = None
|
|
||||||
|
|
||||||
|
|
||||||
# -- Options for HTML output -------------------------------------------------
|
|
||||||
|
|
||||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
|
||||||
# a list of builtin themes.
|
|
||||||
#
|
|
||||||
#html_theme = 'alabaster'
|
|
||||||
# **themes**
|
|
||||||
#html_theme = 'bizstyle'
|
|
||||||
|
|
||||||
# Theme options are theme-specific and customize the look and feel of a theme
|
|
||||||
# further. For a list of options available for each theme, see the
|
|
||||||
# documentation.
|
|
||||||
#
|
|
||||||
# html_theme_options = {}
|
|
||||||
|
|
||||||
# Add any paths that contain custom static files (such as style sheets) here,
|
|
||||||
# relative to this directory. They are copied after the builtin static files,
|
|
||||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
|
||||||
html_static_path = ['_static']
|
|
||||||
|
|
||||||
# Custom sidebar templates, must be a dictionary that maps document names
|
|
||||||
# to template names.
|
|
||||||
#
|
|
||||||
# The default sidebars (for documents that don't match any pattern) are
|
|
||||||
# defined by theme itself. Builtin themes are using these templates by
|
|
||||||
# default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
|
|
||||||
# 'searchbox.html']``.
|
|
||||||
#
|
|
||||||
# html_sidebars = {}
|
|
||||||
|
|
||||||
def setup(app):
|
def setup(app):
|
||||||
app.add_css_file('css/custom.css')
|
app.add_css_file('css/custom.css')
|
||||||
|
|
|
@ -14,3 +14,47 @@ This process describes how to install and run the project locally, e.g. for deve
|
||||||
*Nota*: command is to be executed through the terminal
|
*Nota*: command is to be executed through the terminal
|
||||||
|
|
||||||
`pip install rougail`
|
`pip install rougail`
|
||||||
|
|
||||||
|
Code quality
|
||||||
|
---------------
|
||||||
|
|
||||||
|
We are using `pre-commit <https://pre-commit.com/>`_, there is a :file:`.pre-commit-config.yaml`
|
||||||
|
pre-commit config file in the root's project.
|
||||||
|
|
||||||
|
You need to:
|
||||||
|
|
||||||
|
- install the pre-commit library::
|
||||||
|
|
||||||
|
pip install pre-commit
|
||||||
|
|
||||||
|
- registrer the pre-commit git hooks with this command::
|
||||||
|
|
||||||
|
pre-commit install
|
||||||
|
|
||||||
|
- launch the quality code procedure with::
|
||||||
|
|
||||||
|
pre-commit
|
||||||
|
|
||||||
|
or simply just commit your changes, pre-commit will automatically be launched.
|
||||||
|
|
||||||
|
.. attention:: If an error is found, the commit will not happen.
|
||||||
|
You must resolve all errors that pre-commit that pre-commit points out to you before.
|
||||||
|
|
||||||
|
.. note:: If you need for some reason to disable `pre-commit`, just set
|
||||||
|
the `PRE_COMMIT_ALLOW_NO_CONFIG` environment variable before commiting::
|
||||||
|
|
||||||
|
PRE_COMMIT_ALLOW_NO_CONFIG=1 git commit
|
||||||
|
|
||||||
|
Coding standard
|
||||||
|
------------------
|
||||||
|
|
||||||
|
We use black
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
|
||||||
|
- repo: https://github.com/psf/black
|
||||||
|
rev: 22.10.0
|
||||||
|
hooks:
|
||||||
|
- id: black
|
||||||
|
|
||||||
|
And some YAML and JSON validators.
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
The dictionaries
|
The structure files
|
||||||
=====================
|
=====================
|
||||||
|
|
||||||
What do you mean by :term:`dictionary`?
|
What do you mean by :term:`structure file`?
|
||||||
-------------------------------------------
|
-------------------------------------------
|
||||||
|
|
||||||
A :term:`dictionary` is a YAML file whose structure is described in this documentation page.
|
A :term:`structure file` is a YAML file whose structure is described in this documentation page.
|
||||||
|
|
||||||
A dictionary contains a set of variables loaded into :term:`Tiramisu`, usable at any time, especially in a :term:`templates`.
|
A structure file contains a set of variables loaded into :term:`Tiramisu`, usable at any time, especially in a :term:`templates`.
|
||||||
|
|
||||||
:term:`Families` and :term:`variables` can be defined in several dictionaries. These dictionaries are then aggregated.
|
:term:`Families` and :term:`variables` can be defined in several structure files. These structure files are then aggregated.
|
||||||
|
|
||||||
Dictionaries are loaded in the directory order defined by the `dictionaries_dir` configuration parameter.
|
Structure files are loaded in the directory order defined by the `dictionaries_dir` configuration parameter.
|
||||||
Each directory is loaded one after the other.
|
Each directory is loaded one after the other.
|
||||||
Inside these directories the YAML files will be classified in alphabetical order.
|
Inside these directories the YAML files will be classified in alphabetical order.
|
||||||
|
|
||||||
|
@ -21,11 +21,11 @@ It is also possible to :term:`redefine` elements to change the behavior of a fam
|
||||||
The default namespace
|
The default namespace
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
The families and variables contained in these dictionaries are ordered, by default, in the `rougail` namespace. It is possible to change the name of this namespace :doc:`with the `variable_namespace` parameter of the configuration <configuration>`.
|
The families and variables contained in these structure files are ordered, by default, in the `rougail` namespace. It is possible to change the name of this namespace with the :term:`variable namespace <variable_namespace>` parameter of the :doc:`configuration <configuration>`.
|
||||||
|
|
||||||
This namespace is a bit special, it can access variables in another namespace.
|
This namespace is a bit special, it can access variables in another namespace.
|
||||||
|
|
||||||
The extra dictionaries
|
The extra structure files
|
||||||
---------------------------
|
---------------------------
|
||||||
|
|
||||||
An extra is a different namespace. The idea is to be able to classify the variables by theme.
|
An extra is a different namespace. The idea is to be able to classify the variables by theme.
|
||||||
|
|
69
docs/ext/extinclude.py
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|
||||||
|
from docutils import nodes
|
||||||
|
from sphinx.application import Sphinx
|
||||||
|
from sphinx.util.docutils import SphinxDirective, SphinxRole
|
||||||
|
from sphinx.util.typing import ExtensionMetadata
|
||||||
|
from sphinx.directives.code import LiteralInclude, container_wrapper
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
class ExtInclude(LiteralInclude):
|
||||||
|
"""A directive to include code that comes from an url
|
||||||
|
|
||||||
|
Sample use::
|
||||||
|
|
||||||
|
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_010/firefox/00-proxy.yml
|
||||||
|
:linenos:
|
||||||
|
:language: yaml
|
||||||
|
:caption: this is a interesting code
|
||||||
|
|
||||||
|
- parameter required
|
||||||
|
- linenos, language and caption are optionnal.
|
||||||
|
|
||||||
|
:default language: yaml
|
||||||
|
:default caption: extinclude parameter (url)
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def run(self) -> list[nodes.Node]:
|
||||||
|
url = self.arguments[0]
|
||||||
|
headers = {
|
||||||
|
'accept': 'application/text',
|
||||||
|
'Content-Type': 'application/text',
|
||||||
|
}
|
||||||
|
response = requests.get(url, headers=headers)
|
||||||
|
#paragraph_node = nodes.paragraph(text=f'hello {self.arguments[0]}!')
|
||||||
|
code = response.text
|
||||||
|
|
||||||
|
literal = nodes.literal_block(code, code)
|
||||||
|
if 'language' in self.options:
|
||||||
|
literal['language'] = self.options['language']
|
||||||
|
else:
|
||||||
|
literal['language'] = 'yaml'
|
||||||
|
literal['linenos'] = 'linenos' in self.options
|
||||||
|
if 'caption' in self.options:
|
||||||
|
caption = self.options.get('caption')
|
||||||
|
else:
|
||||||
|
caption = url
|
||||||
|
literal['caption'] = caption
|
||||||
|
if 'name' in self.options:
|
||||||
|
literal['name'] = self.options.get('name')
|
||||||
|
literal = container_wrapper(self, literal, caption)
|
||||||
|
self.add_name(literal)
|
||||||
|
|
||||||
|
return [literal]
|
||||||
|
#paragraph_node = nodes.paragraph(text=content.text)
|
||||||
|
#return [paragraph_node]
|
||||||
|
|
||||||
|
|
||||||
|
def setup(app: Sphinx) -> ExtensionMetadata:
|
||||||
|
|
||||||
|
app.add_directive('extinclude', ExtInclude)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'version': '0.1',
|
||||||
|
'parallel_read_safe': True,
|
||||||
|
'parallel_write_safe': True,
|
||||||
|
}
|
42
docs/ext/xref.py
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
"""adds link url in the global scope
|
||||||
|
"""
|
||||||
|
from docutils import nodes
|
||||||
|
|
||||||
|
from sphinx.util import caption_ref_re
|
||||||
|
|
||||||
|
def xref( typ, rawtext, text, lineno, inliner, options={}, content=[] ):
|
||||||
|
|
||||||
|
title = target = text
|
||||||
|
titleistarget = True
|
||||||
|
# look if explicit title and target are given with `foo <bar>` syntax
|
||||||
|
brace = text.find('<')
|
||||||
|
if brace != -1:
|
||||||
|
titleistarget = False
|
||||||
|
m = caption_ref_re.match(text)
|
||||||
|
if m:
|
||||||
|
target = m.group(2)
|
||||||
|
title = m.group(1)
|
||||||
|
else:
|
||||||
|
# fallback: everything after '<' is the target
|
||||||
|
target = text[brace+1:]
|
||||||
|
title = text[:brace]
|
||||||
|
|
||||||
|
link = xref.links[target]
|
||||||
|
|
||||||
|
if brace != -1:
|
||||||
|
pnode = nodes.reference(target, title, refuri=link[1])
|
||||||
|
else:
|
||||||
|
pnode = nodes.reference(target, link[0], refuri=link[1])
|
||||||
|
|
||||||
|
return [pnode], []
|
||||||
|
|
||||||
|
def get_refs(app):
|
||||||
|
|
||||||
|
xref.links = app.config.xref_links
|
||||||
|
|
||||||
|
def setup(app):
|
||||||
|
|
||||||
|
app.add_config_value('xref_links', {}, True)
|
||||||
|
app.add_role('xref', xref)
|
||||||
|
app.connect("builder-inited", get_refs)
|
||||||
|
|
111
docs/family.rst
|
@ -1,24 +1,49 @@
|
||||||
A family
|
The families
|
||||||
============
|
=============
|
||||||
|
|
||||||
Synopsis
|
Synopsis
|
||||||
---------
|
---------
|
||||||
|
|
||||||
A family is a container of variables and subfamily.
|
.. glossary::
|
||||||
|
|
||||||
|
family
|
||||||
|
|
||||||
|
A family of variables is simply a collection of variables that refer to
|
||||||
|
the same business model category. It's just a variables container.
|
||||||
|
Think of it as a container as well as a namespace.
|
||||||
|
|
||||||
.. attention:: A family without a subfamily or subvariable will be automatically deleted.
|
.. attention:: A family without a subfamily or subvariable will be automatically deleted.
|
||||||
|
|
||||||
Name
|
Naming conventions
|
||||||
-------------
|
------------------------
|
||||||
|
|
||||||
It is with this name that we will be able to interact with the family.
|
It is with its name that we will be able to interact with the family.
|
||||||
|
|
||||||
It's best to follow the :ref:`convention on variable names`.
|
.. seealso::
|
||||||
|
|
||||||
|
Have a look at the :ref:`convention on variable names`.
|
||||||
|
|
||||||
|
Shorthand declaration
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
Shorthand declaration is a way to declare a family in a single line. But you can only define family name and description.
|
||||||
|
|
||||||
|
To create a family, just add a key with it's name and variables as values. Attention, do not declare any other attributs.
|
||||||
|
|
||||||
|
By default, the description of the variable is the family name.
|
||||||
|
If you add comment in same line of name, this comment is use as description:
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
|
||||||
|
---
|
||||||
|
version: '1.1'
|
||||||
|
my_family: # This is a great family
|
||||||
|
variable:
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
.. FIXME: faire une page sur la "convention on variable names"
|
.. todo:: faire une page sur la "convention on variable names"
|
||||||
|
|
||||||
.. list-table::
|
.. list-table::
|
||||||
:widths: 15 45
|
:widths: 15 45
|
||||||
|
@ -92,20 +117,13 @@ Parameters
|
||||||
Dynamically created family
|
Dynamically created family
|
||||||
-----------------------------
|
-----------------------------
|
||||||
|
|
||||||
To create a family dynamically, you must create a fictitious family linked to a variable.
|
To create a family dynamically, you must create a fictitious family linked to a calculation.
|
||||||
The family name and description will actually be the prefix of the new name / description.
|
The family name will actually be the prefix of the new name. Alternativly you can specify the suffix in the name, ie `my_{{ suffix }}_name`.
|
||||||
The suffix will come from the value of the bound variable.
|
The suffix will come from the calculation.
|
||||||
The name of the families and variables it contains will be preserved, however, the description will be the prefix of the real description.
|
|
||||||
|
|
||||||
Obviously if the content of the linked variable were to evolve, new dynamic families will appear or disappear.
|
Obviously if the result of calculation were to evolve, new dynamic families will appear or disappear.
|
||||||
|
|
||||||
To note that:
|
Leader or follower variable
|
||||||
|
|
||||||
- the variable linked to the family must be a multiple variable
|
|
||||||
- it is not possible to put a simple family into a dynamic family
|
|
||||||
- it is possible to put a leading family into a dynamic family
|
|
||||||
|
|
||||||
Leader or foller variable
|
|
||||||
-----------------------------
|
-----------------------------
|
||||||
|
|
||||||
A leader family has a typical attribute of “leadership”. The type is required.
|
A leader family has a typical attribute of “leadership”. The type is required.
|
||||||
|
@ -150,12 +168,12 @@ If a leader variable is hidden or disabled, the follower variables will be hidde
|
||||||
Examples
|
Examples
|
||||||
----------
|
----------
|
||||||
|
|
||||||
Simple family
|
Simple family:
|
||||||
|
|
||||||
.. code-block:: yaml
|
.. code-block:: yaml
|
||||||
|
|
||||||
---
|
---
|
||||||
version: '1.0'
|
version: '1.1'
|
||||||
my_family:
|
my_family:
|
||||||
type: family
|
type: family
|
||||||
description: This is a great family
|
description: This is a great family
|
||||||
|
@ -168,7 +186,7 @@ Dynamically created family
|
||||||
.. code-block:: yaml
|
.. code-block:: yaml
|
||||||
|
|
||||||
---
|
---
|
||||||
version: '1.0'
|
version: '1.1'
|
||||||
varname:
|
varname:
|
||||||
multi: true
|
multi: true
|
||||||
default:
|
default:
|
||||||
|
@ -176,18 +194,48 @@ Dynamically created family
|
||||||
- val2
|
- val2
|
||||||
my_dyn_family_:
|
my_dyn_family_:
|
||||||
type: dynamic
|
type: dynamic
|
||||||
variable: rougail.varname
|
dynamic:
|
||||||
description: 'Describe '
|
type: variable
|
||||||
|
variable: rougail.varname
|
||||||
|
description: 'Describe'
|
||||||
my_dyn_var:
|
my_dyn_var:
|
||||||
type: string
|
type: string
|
||||||
description: 'Variable description for '
|
description: 'Variable description'
|
||||||
|
|
||||||
This will dynamically create two families:
|
This will dynamically create two families:
|
||||||
|
|
||||||
- "rougail.my_dyn_family_val1" with the description "Describe val1"
|
- "rougail.my_dyn_family_val1"
|
||||||
- "rougail.my_dyn_family_val2" with the description "Describe val2"
|
- "rougail.my_dyn_family_val2"
|
||||||
|
|
||||||
In the dynamic family "rougail.my_dyn_family_val1" we will find a variable "my_dyn_var" with the description "Variable description for val1".
|
In the dynamic family "rougail.my_dyn_family_val1" we will find a variable "my_dyn_var".
|
||||||
|
|
||||||
|
Here is a second example:
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
|
||||||
|
---
|
||||||
|
version: '1.1'
|
||||||
|
varname:
|
||||||
|
multi: true
|
||||||
|
default:
|
||||||
|
- val1
|
||||||
|
- val2
|
||||||
|
my_dyn_{{ suffix }}_family:
|
||||||
|
type: dynamic
|
||||||
|
dynamic:
|
||||||
|
type: variable
|
||||||
|
variable: rougail.varname
|
||||||
|
description: 'Describe'
|
||||||
|
my_dyn_var:
|
||||||
|
type: string
|
||||||
|
description: 'Variable description'
|
||||||
|
|
||||||
|
This will dynamically create two families:
|
||||||
|
|
||||||
|
- "rougail.my_dyn_val1_family"
|
||||||
|
- "rougail.my_dyn_val2_family"
|
||||||
|
|
||||||
|
In the dynamic family "rougail.my_dyn_val1_family" we will find a variable "my_dyn_var".
|
||||||
|
|
||||||
Leader or follower variable
|
Leader or follower variable
|
||||||
-------------------------------
|
-------------------------------
|
||||||
|
@ -200,7 +248,7 @@ Here is an example of defining a leading variable and two following variables:
|
||||||
.. code-block:: yaml
|
.. code-block:: yaml
|
||||||
|
|
||||||
---
|
---
|
||||||
version: '1.0'
|
version: '1.1'
|
||||||
family:
|
family:
|
||||||
type: leadership
|
type: leadership
|
||||||
leader:
|
leader:
|
||||||
|
@ -217,7 +265,6 @@ To add a new follower variable, in a new dictionary, simply define one or more n
|
||||||
.. code-block:: yaml
|
.. code-block:: yaml
|
||||||
|
|
||||||
---
|
---
|
||||||
version: '1.0'
|
version: '1.1'
|
||||||
family:
|
family:
|
||||||
follower3:
|
follower3:
|
||||||
|
|
||||||
|
|
173
docs/fill.rst
|
@ -48,11 +48,11 @@ Depending on the types of calculation, the parameters will be different:
|
||||||
|
|
||||||
`mandatory`
|
`mandatory`
|
||||||
- Template Jinja. For a multiple variable, each line represents a value.
|
- Template Jinja. For a multiple variable, each line represents a value.
|
||||||
- `{% if rougail.variable %}
|
- `{% if rougail.variable %}`
|
||||||
|
|
||||||
{{ rougail.variable }}
|
`{{ rougail.variable }}`
|
||||||
|
|
||||||
{% endif %}`
|
`{% endif %}`
|
||||||
* - Jinja
|
* - Jinja
|
||||||
- **params**
|
- **params**
|
||||||
|
|
||||||
|
@ -156,6 +156,89 @@ There are two types of parameter:
|
||||||
- Name of the information whose value we want to retrieve.
|
- Name of the information whose value we want to retrieve.
|
||||||
- doc
|
- doc
|
||||||
|
|
||||||
|
The variable path
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
Normal family
|
||||||
|
~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The default namespace is defined in RougailConfig["variable_namespace"] with the default value "rougail".
|
||||||
|
In addition, there are extras namespaces defined with in RougailConfig["extra_dictionaries"].
|
||||||
|
|
||||||
|
Inside those namespaces we can add families and variables.
|
||||||
|
|
||||||
|
Here is an hierarchic examples:
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
rougail
|
||||||
|
├── variable1
|
||||||
|
├── family1
|
||||||
|
│ ├── variable2
|
||||||
|
│ └── variable3
|
||||||
|
└── family2
|
||||||
|
└── subfamily1
|
||||||
|
└── variable4
|
||||||
|
extra1
|
||||||
|
└── family3
|
||||||
|
├── variable5
|
||||||
|
└── variable6
|
||||||
|
|
||||||
|
In `calculation` we can use other variables.
|
||||||
|
|
||||||
|
Here is all paths:
|
||||||
|
|
||||||
|
- rougail.variable1
|
||||||
|
- rougail.family1.variable2
|
||||||
|
- rougail.family1.variable3
|
||||||
|
- rougail.family2.subfamily1.variable4
|
||||||
|
- extra1.family3.variable5
|
||||||
|
- extra1.family3.variable6
|
||||||
|
|
||||||
|
Inside a variable's `calculation` we can use relative path. "_" means that other variable is in same family. "__" means that other variables are in parent family, and so on...
|
||||||
|
|
||||||
|
For example, in variable2's `calculation`, we can use relative path:
|
||||||
|
|
||||||
|
- __.variable1
|
||||||
|
- _.variable3
|
||||||
|
- __.family2.subfamily1.variable4
|
||||||
|
|
||||||
|
But we cannot access to extra1 variables with relative path.
|
||||||
|
|
||||||
|
Dynamic family
|
||||||
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Hire is a dynamic family "{{ suffix }}":
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
rougail
|
||||||
|
├── variable1: ["val1", "val2"]
|
||||||
|
├── {{ suffix }}
|
||||||
|
│ ├── variable2
|
||||||
|
│ └── variable3
|
||||||
|
└── family
|
||||||
|
└── variable4
|
||||||
|
|
||||||
|
For variable2's calculation, we can use:
|
||||||
|
|
||||||
|
- rougail.{{ suffix }}.variable3
|
||||||
|
- _.variable3
|
||||||
|
|
||||||
|
In this case, we get value for "variable3" with the same suffix as "variable2".
|
||||||
|
|
||||||
|
For variable4's calculation, we have two possibility:
|
||||||
|
|
||||||
|
- retrieves all values with all suffixes:
|
||||||
|
|
||||||
|
- rougail.{{ suffix }}.variable3
|
||||||
|
- __.{{ suffix }}.variable3
|
||||||
|
|
||||||
|
- retrieves a value for a specified suffix:
|
||||||
|
|
||||||
|
- rougail.val1.variable3
|
||||||
|
- __.val1.variable3
|
||||||
|
|
||||||
Examples
|
Examples
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
|
@ -167,7 +250,7 @@ Let's start with an example from a simple Jinja template:
|
||||||
.. code-block:: yaml
|
.. code-block:: yaml
|
||||||
|
|
||||||
---
|
---
|
||||||
version: '1.0'
|
version: '1.1'
|
||||||
my_calculated_variable:
|
my_calculated_variable:
|
||||||
default:
|
default:
|
||||||
type: jinja
|
type: jinja
|
||||||
|
@ -178,7 +261,7 @@ Here is a second example with a boolean variable:
|
||||||
.. code-block:: yaml
|
.. code-block:: yaml
|
||||||
|
|
||||||
---
|
---
|
||||||
version: '1.0'
|
version: '1.1'
|
||||||
my_calculated_variable:
|
my_calculated_variable:
|
||||||
type: boolean
|
type: boolean
|
||||||
default:
|
default:
|
||||||
|
@ -190,7 +273,7 @@ And a multiple value of the number type:
|
||||||
.. code-block:: yaml
|
.. code-block:: yaml
|
||||||
|
|
||||||
---
|
---
|
||||||
version: '1.0'
|
version: '1.1'
|
||||||
my_calculated_variable:
|
my_calculated_variable:
|
||||||
type: number
|
type: number
|
||||||
multi: true
|
multi: true
|
||||||
|
@ -206,7 +289,7 @@ Let's create a variable whose value is returned by a python function:
|
||||||
.. code-block:: yaml
|
.. code-block:: yaml
|
||||||
|
|
||||||
---
|
---
|
||||||
version: '1.0'
|
version: '1.1'
|
||||||
my_calculated_variable:
|
my_calculated_variable:
|
||||||
default:
|
default:
|
||||||
type: jinja
|
type: jinja
|
||||||
|
@ -224,7 +307,7 @@ An example with parameters:
|
||||||
.. code-block:: yaml
|
.. code-block:: yaml
|
||||||
|
|
||||||
---
|
---
|
||||||
version: '1.0'
|
version: '1.1'
|
||||||
my_calculated_variable:
|
my_calculated_variable:
|
||||||
description: my description
|
description: my description
|
||||||
default:
|
default:
|
||||||
|
@ -235,19 +318,19 @@ An example with parameters:
|
||||||
param1: value
|
param1: value
|
||||||
param2:
|
param2:
|
||||||
type: variable
|
type: variable
|
||||||
variable: rougail.unknown_variable
|
variable: _.unknown_variable
|
||||||
optional: true
|
optional: true
|
||||||
param3:
|
param3:
|
||||||
type: information
|
type: information
|
||||||
information: doc
|
information: doc
|
||||||
variable: rougail.my_calculated_variable
|
variable: _.my_calculated_variable
|
||||||
|
|
||||||
An example with a `suffix` type parameter:
|
An example with a `suffix` type parameter:
|
||||||
|
|
||||||
.. code-block:: yaml
|
.. code-block:: yaml
|
||||||
|
|
||||||
---
|
---
|
||||||
version: '1.0'
|
version: '1.1'
|
||||||
varname:
|
varname:
|
||||||
multi: true
|
multi: true
|
||||||
default:
|
default:
|
||||||
|
@ -255,7 +338,9 @@ An example with a `suffix` type parameter:
|
||||||
- val2
|
- val2
|
||||||
my_dyn_family_:
|
my_dyn_family_:
|
||||||
type: dynamic
|
type: dynamic
|
||||||
variable: rougail.varname
|
dynamic:
|
||||||
|
type: variable
|
||||||
|
variable: _.varname
|
||||||
description: 'Describe '
|
description: 'Describe '
|
||||||
my_dyn_var:
|
my_dyn_var:
|
||||||
type: string
|
type: string
|
||||||
|
@ -275,7 +360,7 @@ An example with an index type parameter:
|
||||||
.. code-block:: yaml
|
.. code-block:: yaml
|
||||||
|
|
||||||
---
|
---
|
||||||
version: '1.0'
|
version: '1.1'
|
||||||
family:
|
family:
|
||||||
type: leadership
|
type: leadership
|
||||||
leader:
|
leader:
|
||||||
|
@ -299,7 +384,7 @@ Copy a variable in another:
|
||||||
.. code-block:: yaml
|
.. code-block:: yaml
|
||||||
|
|
||||||
---
|
---
|
||||||
version: '1.0'
|
version: '1.1'
|
||||||
my_variable:
|
my_variable:
|
||||||
multi: true
|
multi: true
|
||||||
default:
|
default:
|
||||||
|
@ -309,14 +394,32 @@ Copy a variable in another:
|
||||||
multi: true
|
multi: true
|
||||||
default:
|
default:
|
||||||
type: variable
|
type: variable
|
||||||
variable: rougail.my_variable
|
variable: _.my_variable
|
||||||
|
|
||||||
|
Copy the default value from a variable, means copy type, params and multi attribute too if not define in second variable.
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
|
||||||
|
---
|
||||||
|
version: 1.1
|
||||||
|
my_variable:
|
||||||
|
multi: true
|
||||||
|
type: domainname
|
||||||
|
params:
|
||||||
|
allow_ip: true
|
||||||
|
my_calculated_variable:
|
||||||
|
default:
|
||||||
|
type: variable
|
||||||
|
variable: _.var1
|
||||||
|
|
||||||
|
Here my_calculated_variable is a domainname variable with parameter allow_ip=True and multi to true.
|
||||||
|
|
||||||
Copy one variable to another if the source has no `property` problem:
|
Copy one variable to another if the source has no `property` problem:
|
||||||
|
|
||||||
.. code-block:: yaml
|
.. code-block:: yaml
|
||||||
|
|
||||||
---
|
---
|
||||||
version: '1.0'
|
version: '1.1'
|
||||||
my_variable:
|
my_variable:
|
||||||
default: val1
|
default: val1
|
||||||
disabled: true
|
disabled: true
|
||||||
|
@ -324,7 +427,7 @@ Copy one variable to another if the source has no `property` problem:
|
||||||
multi: true
|
multi: true
|
||||||
default:
|
default:
|
||||||
type: variable
|
type: variable
|
||||||
variable: rougail.my_variable
|
variable: _.my_variable
|
||||||
propertyerror: false
|
propertyerror: false
|
||||||
|
|
||||||
Copy two non-multiple variables into a multiple variable:
|
Copy two non-multiple variables into a multiple variable:
|
||||||
|
@ -332,7 +435,7 @@ Copy two non-multiple variables into a multiple variable:
|
||||||
.. code-block:: yaml
|
.. code-block:: yaml
|
||||||
|
|
||||||
---
|
---
|
||||||
version: '1.0'
|
version: '1.1'
|
||||||
my_variable_1:
|
my_variable_1:
|
||||||
default: val1
|
default: val1
|
||||||
my_variable_2:
|
my_variable_2:
|
||||||
|
@ -341,9 +444,9 @@ Copy two non-multiple variables into a multiple variable:
|
||||||
multi: true
|
multi: true
|
||||||
default:
|
default:
|
||||||
- type: variable
|
- type: variable
|
||||||
variable: rougail.my_variable_1
|
variable: _.my_variable_1
|
||||||
- type: variable
|
- type: variable
|
||||||
variable: rougail.my_variable_2
|
variable: _.my_variable_2
|
||||||
|
|
||||||
A variable in a dynamic family can also be used in a calculation.
|
A variable in a dynamic family can also be used in a calculation.
|
||||||
|
|
||||||
|
@ -352,7 +455,7 @@ For example using the variable for a particular suffix:
|
||||||
.. code-block:: yaml
|
.. code-block:: yaml
|
||||||
|
|
||||||
---
|
---
|
||||||
version: '1.0'
|
version: '1.1'
|
||||||
varname:
|
varname:
|
||||||
multi: true
|
multi: true
|
||||||
default:
|
default:
|
||||||
|
@ -360,7 +463,9 @@ For example using the variable for a particular suffix:
|
||||||
- val2
|
- val2
|
||||||
my_dyn_family_:
|
my_dyn_family_:
|
||||||
type: dynamic
|
type: dynamic
|
||||||
variable: rougail.varname
|
dynamic:
|
||||||
|
type: variable
|
||||||
|
variable: _.varname
|
||||||
description: 'Describe '
|
description: 'Describe '
|
||||||
my_dyn_var_:
|
my_dyn_var_:
|
||||||
type: string
|
type: string
|
||||||
|
@ -369,7 +474,7 @@ For example using the variable for a particular suffix:
|
||||||
all_dyn_var:
|
all_dyn_var:
|
||||||
default:
|
default:
|
||||||
type: variable
|
type: variable
|
||||||
variable: rougail.my_dyn_family_val1.my_dyn_var_val1
|
variable: _.my_dyn_family_val1.my_dyn_var_val1
|
||||||
|
|
||||||
In this case, we recover the value `val1`.
|
In this case, we recover the value `val1`.
|
||||||
|
|
||||||
|
@ -378,7 +483,7 @@ Second example using the variable for all suffixes:
|
||||||
.. code-block:: yaml
|
.. code-block:: yaml
|
||||||
|
|
||||||
---
|
---
|
||||||
version: '1.0'
|
version: '1.1'
|
||||||
varname:
|
varname:
|
||||||
multi: true
|
multi: true
|
||||||
default:
|
default:
|
||||||
|
@ -386,7 +491,9 @@ Second example using the variable for all suffixes:
|
||||||
- val2
|
- val2
|
||||||
my_dyn_family_:
|
my_dyn_family_:
|
||||||
type: dynamic
|
type: dynamic
|
||||||
variable: rougail.varname
|
dynamic:
|
||||||
|
type: variable
|
||||||
|
variable: _.varname
|
||||||
description: 'Describe '
|
description: 'Describe '
|
||||||
my_dyn_var_:
|
my_dyn_var_:
|
||||||
type: string
|
type: string
|
||||||
|
@ -396,7 +503,7 @@ Second example using the variable for all suffixes:
|
||||||
multi: true
|
multi: true
|
||||||
default:
|
default:
|
||||||
type: variable
|
type: variable
|
||||||
variable: rougail.my_dyn_family_.my_dyn_var_
|
variable: _.my_dyn_family_.my_dyn_var_
|
||||||
|
|
||||||
In this case, we recover the `val1` and `val2` list.
|
In this case, we recover the `val1` and `val2` list.
|
||||||
|
|
||||||
|
@ -406,7 +513,7 @@ Calculation via a suffix
|
||||||
.. code-block:: yaml
|
.. code-block:: yaml
|
||||||
|
|
||||||
---
|
---
|
||||||
version: '1.0'
|
version: '1.1'
|
||||||
varname:
|
varname:
|
||||||
multi: true
|
multi: true
|
||||||
default:
|
default:
|
||||||
|
@ -414,7 +521,9 @@ Calculation via a suffix
|
||||||
- val2
|
- val2
|
||||||
my_dyn_family_:
|
my_dyn_family_:
|
||||||
type: dynamic
|
type: dynamic
|
||||||
variable: rougail.varname
|
dynamic:
|
||||||
|
type: variable
|
||||||
|
variable: _.varname
|
||||||
description: 'Describe '
|
description: 'Describe '
|
||||||
my_dyn_var_:
|
my_dyn_var_:
|
||||||
type: string
|
type: string
|
||||||
|
@ -427,7 +536,7 @@ Calculation via an index
|
||||||
.. code-block:: yaml
|
.. code-block:: yaml
|
||||||
|
|
||||||
---
|
---
|
||||||
version: '1.0'
|
version: '1.1'
|
||||||
family:
|
family:
|
||||||
type: leadership
|
type: leadership
|
||||||
leader:
|
leader:
|
||||||
|
@ -448,7 +557,7 @@ In a first dictionary, let's declare our variable and our calculation:
|
||||||
.. code-block:: yaml
|
.. code-block:: yaml
|
||||||
|
|
||||||
---
|
---
|
||||||
version: '1.0'
|
version: '1.1'
|
||||||
my_calculated_variable:
|
my_calculated_variable:
|
||||||
default:
|
default:
|
||||||
type: jinja
|
type: jinja
|
||||||
|
@ -459,7 +568,7 @@ In a second dictionary, it is possible to redefine the calculation:
|
||||||
.. code-block:: yaml
|
.. code-block:: yaml
|
||||||
|
|
||||||
---
|
---
|
||||||
version: '1.0'
|
version: '1.1'
|
||||||
my_calculated_variable:
|
my_calculated_variable:
|
||||||
redefine: true
|
redefine: true
|
||||||
default:
|
default:
|
||||||
|
@ -471,7 +580,7 @@ In a third dictionary, we even can delete the calculation if needed:
|
||||||
.. code-block:: yaml
|
.. code-block:: yaml
|
||||||
|
|
||||||
---
|
---
|
||||||
version: '1.0'
|
version: '1.1'
|
||||||
my_calculated_variable:
|
my_calculated_variable:
|
||||||
redefine: true
|
redefine: true
|
||||||
default: null
|
default: null
|
||||||
|
|
|
@ -1,175 +1,135 @@
|
||||||
.. |Tiramisu| replace:: Tiramisu
|
|
||||||
.. _tiramisu: https://forge.cloud.silique.fr/stove/tiramisu
|
|
||||||
|
|
||||||
Getting started
|
Getting started
|
||||||
====================
|
====================
|
||||||
|
|
||||||
|
.. _installation:
|
||||||
|
|
||||||
|
Installation
|
||||||
|
----------------
|
||||||
|
|
||||||
|
You can use the python installer and type the install command in your preferred terminal:
|
||||||
|
|
||||||
|
.. code-block:: text
|
||||||
|
|
||||||
|
pip install rougail
|
||||||
|
|
||||||
|
a better way to install the Rougail library in to do this in a virtual environment:
|
||||||
|
|
||||||
|
.. code-block:: text
|
||||||
|
|
||||||
|
python -m"venv' .venv
|
||||||
|
|
||||||
|
And, under linux or Mac:
|
||||||
|
|
||||||
|
.. code-block:: shell
|
||||||
|
|
||||||
|
./.venv/bin/activate
|
||||||
|
|
||||||
|
or, under windows:
|
||||||
|
|
||||||
|
.. code-block:: text
|
||||||
|
|
||||||
|
.venv\Scripts\activate.exe
|
||||||
|
|
||||||
|
|
||||||
What is a consistency handling system ?
|
What is a consistency handling system ?
|
||||||
------------------------------------------------
|
------------------------------------------------
|
||||||
|
|
||||||
.. questions:: Question: "OK, I have understood that the Rougail stuff enables me to take advantage of |Tiramisu|. But what is all this for? What is exactly a consistency handling system? And again, what is this |Tiramisu| library used for?"
|
.. questions:: Question
|
||||||
|
|
||||||
*Answer*: Well, let's explain what |Tiramisu| is and how we are using the |Tiramisu| library.
|
**Question**: OK, I have understood that the Rougail library enables me to take advantage of the :xref:`tiramisu` library. But what is all this for? What is exactly a consistency handling system? And again, what is this :xref:`tiramisu library` used for?
|
||||||
|
|
||||||
|
**Answer**: Well, now we explain what this :xref:`tiramisu` library is, and how we are using it.
|
||||||
|
|
||||||
.. glossary::
|
.. glossary::
|
||||||
|
|
||||||
Tiramisu
|
Tiramisu
|
||||||
|
|
||||||
|Tiramisu| is a consistency handling system that was initially designed
|
:xref:`tiramisu` is a consistency handling system that has initially been designed
|
||||||
in the configuration management scope. To put it more simply,
|
in the configuration management scope. To put it more simply,
|
||||||
this library is generally used to handle configuration options.
|
this library is generally used to handle configuration options.
|
||||||
|
|
||||||
It manages variables and group of variables. In the Tiramisu scope we call
|
It manages variables and group of variables. In the Tiramisu scope we call
|
||||||
it *options* and *option descriptions*.
|
it *options* and *option descriptions*.
|
||||||
|
|
||||||
|
Here is the :xref:`tiramisu documentation <tiramisu>`.
|
||||||
|
|
||||||
In the Rougail scope, we call it :term:`variable`\ s and :term:`families`.
|
In the Rougail scope, we call it :term:`variable`\ s and :term:`families`.
|
||||||
In Rougail, the families and variables are located in the :term:`dictionaries`.
|
In Rougail, the families and variables are located in the :term:`structure files <structure file>`.
|
||||||
|
|
||||||
And this is what we are going to explain in this page.
|
The structure files
|
||||||
|
|
||||||
The dictionaries
|
|
||||||
---------------------
|
---------------------
|
||||||
|
|
||||||
.. glossary::
|
.. glossary::
|
||||||
|
|
||||||
dictionary
|
structure file
|
||||||
dictionaries
|
|
||||||
|
|
||||||
A dictionary in the Rougail meaning is a YAML file that describes variables
|
A structure file in the Rougail meaning is a YAML file that describes variables
|
||||||
and their dependencies / consistencies.
|
and their dependencies / consistencies.
|
||||||
There can be a lot of dictionary files located in many different folders.
|
There can be a lot of structure files located in many different folders.
|
||||||
|
|
||||||
Rougail reads all the dictionaries and loads them into a single object
|
Rougail reads all the dictionaries and loads them into a single object
|
||||||
that handles the variables consistency.
|
that handles the variables consistency.
|
||||||
|
|
||||||
.. image:: images/schema.png
|
.. image:: images/schema.png
|
||||||
|
|
||||||
The main advantage is that declaring variables and writing consistency is a simple
|
The main advantage is that declaring variables and writing consistency is as simple
|
||||||
as writing YAML. It is not necessary to write :term:`Tiramisu` code.
|
as writing YAML. With Rougail it is not necessary to write :term:`Tiramisu` code any more.
|
||||||
It simplifies a lot of things.
|
It simplifies a lot of things.
|
||||||
|
|
||||||
And rather than writing :term:`Tiramisu` code, we can declare variables and describe the relationships between variables in a declarative mode.
|
And rather than writing :term:`Tiramisu` code, we can declare variables and describe the relationships between variables in a declarative style (that is, in a YAML file).
|
||||||
|
|
||||||
Once the dictionaries are loaded by Rougail, we find all the power of the :term:`Tiramisu` configuration management tool.
|
Once the structure files are loaded by Rougail, the Tiramisu configuration management tool can check the consistency of the variables between them.
|
||||||
|
|
||||||
The dictionaries YAML format
|
|
||||||
---------------------------------
|
|
||||||
|
|
||||||
Before getting started with Rougail we need to learn the specifics of the YAML dictionaries file format (as well as some templating concepts).
|
|
||||||
|
|
||||||
.. FIXME parler de jinja https://jinja.palletsprojects.com
|
|
||||||
|
|
||||||
Here is a :term:`dictionary` example:
|
|
||||||
|
|
||||||
.. code-block:: yaml
|
|
||||||
:linenos:
|
|
||||||
|
|
||||||
---
|
|
||||||
version: '1.0'
|
|
||||||
proxy:
|
|
||||||
description: Configure Proxy Access to the Internet
|
|
||||||
type: family
|
|
||||||
|
|
||||||
Line 3, we declare a **variable** named `proxy` with his `description` line 4 and his `type` line 5.
|
|
||||||
|
|
||||||
The variables
|
The variables
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
|
Here is a :term:`structure file` example with only a variable **variable** named `proxy_mode`
|
||||||
|
A variable can be defined with no default value at all.
|
||||||
|
|
||||||
|
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_011/firefox/00-proxy.yml
|
||||||
|
:language: yaml
|
||||||
|
:caption: A Rougail dictionnary file with a variable named `proxy_mode`, with a description.
|
||||||
|
:name: RougailDictionaryFirstVariableDescription
|
||||||
|
|
||||||
|
..
|
||||||
|
---
|
||||||
|
proxy_mode:
|
||||||
|
description: Configure Proxy Access to the Internet
|
||||||
|
|
||||||
|
The same with a default value:
|
||||||
|
|
||||||
|
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_012/firefox/00-proxy.yml
|
||||||
|
:language: yaml
|
||||||
|
:caption: A Rougail dictionnary file with a variable named `proxy_mode`, with a default value.
|
||||||
|
:name: RougailDictionaryFirstVariableDefault
|
||||||
|
|
||||||
|
..
|
||||||
|
---
|
||||||
|
proxy_mode:
|
||||||
|
description: Configure Proxy Access to the Internet
|
||||||
|
default: No proxy
|
||||||
|
|
||||||
variable
|
variable
|
||||||
|
|
||||||
Here is a second definition of a :term:`variable`: it is a declaration unit that represents a business domain metaphor,
|
A :term:`variable` is a declaration unit that represents a business domain metaphor,
|
||||||
|
the most common example is that a variable that represents a configuration option
|
||||||
the most common example is that a variable represents a configuration option
|
|
||||||
in a application, but a variable represents something more that a configuration option.
|
in a application, but a variable represents something more that a configuration option.
|
||||||
It provides a business domain specific representation unit.
|
It provides a business domain specific representation unit.
|
||||||
|
|
||||||
.. note:: dictionaries can just define a list of variables, but we will see that
|
|
||||||
we can specify a lot more. We can define variables **and** their relations,
|
|
||||||
and the consistency between them.
|
|
||||||
|
|
||||||
In the next step, we will explain through a tutorial how to construct a list of variables.
|
|
||||||
|
|
||||||
Families of variables
|
Families of variables
|
||||||
--------------------------
|
--------------------------
|
||||||
|
|
||||||
.. glossary::
|
family
|
||||||
|
|
||||||
family
|
A :term:`family` is simply a container of variables and/ore some subfamilies.
|
||||||
families
|
|
||||||
|
|
||||||
A family of variables is simply a collection of variables that refer to
|
Here how a YAML structure file with a family looks like:
|
||||||
the same business model category. It's just a variables container.
|
|
||||||
Think of it as a container as well as a namespace.
|
|
||||||
|
|
||||||
A "hello world" with Rougail
|
|
||||||
------------------------------
|
|
||||||
|
|
||||||
We're gonna make the simplest possible example.
|
|
||||||
|
|
||||||
.. prerequisites:: Prerequisites
|
|
||||||
|
|
||||||
We assume that Rougail's library is installed on your computer (or in a virtual environment).
|
|
||||||
|
|
||||||
.. exercise:: Let's make a Hello world
|
|
||||||
|
|
||||||
Here is the tree structure we want to have::
|
|
||||||
|
|
||||||
workplace
|
|
||||||
├── dict
|
|
||||||
│ ├── hello.yml
|
|
||||||
└── hello.py
|
|
||||||
|
|
||||||
- Let's make a :file:`workplace` directory, with a :file:`dict` subfolder.
|
|
||||||
- First, we need a :term:`dictionary`, so let's make the :file:`hello.yml` file
|
|
||||||
which is located in the :file:`dict` subfolder, with the following content:
|
|
||||||
|
|
||||||
.. code-block:: yaml
|
.. code-block:: yaml
|
||||||
:caption: The `hello.yaml` file
|
:caption: A :file:`hello.yml` structure sample file
|
||||||
|
|
||||||
---
|
---
|
||||||
version: '1.0'
|
version: '1.1'
|
||||||
hello:
|
|
||||||
default: world
|
|
||||||
|
|
||||||
- Then we make a :file:`hello.py` in our root :file:`workplace` directory:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
:caption: The :file:`hello.py` file
|
|
||||||
|
|
||||||
from rougail import Rougail, RougailConfig
|
|
||||||
|
|
||||||
RougailConfig['dictionaries_dir'] = ['dict']
|
|
||||||
rougail = Rougail()
|
|
||||||
config = rougail.get_config()
|
|
||||||
print(config.value.get())
|
|
||||||
|
|
||||||
.. demo:: Let's run the :file:`hello.py` script
|
|
||||||
|
|
||||||
We launch the script:
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
python hello.py
|
|
||||||
|
|
||||||
And we obtain the following result:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
{'rougail.hello': 'world'}
|
|
||||||
|
|
||||||
**Congratulations ! You have successfully completed your first Rougail script.**
|
|
||||||
|
|
||||||
A "Hello, <name> " sample with a family
|
|
||||||
------------------------------------------
|
|
||||||
|
|
||||||
Let's continuing on our "Hello world" theme and add a :term:`family` container.
|
|
||||||
|
|
||||||
.. code-block:: yaml
|
|
||||||
:caption: the :file:`hello.yml` file
|
|
||||||
:linenos:
|
|
||||||
|
|
||||||
---
|
|
||||||
version: '1.0'
|
|
||||||
world:
|
world:
|
||||||
description: Hello world family container
|
description: Hello world family container
|
||||||
name:
|
name:
|
||||||
|
@ -179,14 +139,3 @@ Let's continuing on our "Hello world" theme and add a :term:`family` container.
|
||||||
Here, we have a family named `world`.
|
Here, we have a family named `world`.
|
||||||
This family contains a variable named `name`
|
This family contains a variable named `name`
|
||||||
|
|
||||||
Again, let's validate this YAML file against Rougail's API:
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
python hello.py
|
|
||||||
|
|
||||||
We then have the output:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
{'rougail.world.name': 'rougail'}
|
|
||||||
|
|
BIN
docs/images/QuestionaryChoice.png
Normal file
After Width: | Height: | Size: 53 KiB |
BIN
docs/images/UserDataOutput.png
Normal file
After Width: | Height: | Size: 83 KiB |
BIN
docs/images/tiramisu_get_set.png
Normal file
After Width: | Height: | Size: 88 KiB |
|
@ -8,11 +8,34 @@
|
||||||
Rougail
|
Rougail
|
||||||
===========
|
===========
|
||||||
|
|
||||||
|
.. todolist::
|
||||||
|
|
||||||
|
|
||||||
|
.. todo:: définir les termes suivants
|
||||||
|
|
||||||
|
family.rst:25: WARNING: label non défini: 'convention on variable names'
|
||||||
|
/family.rst:114: WARNING: term not in glossary: 'calculation'
|
||||||
|
/variable.rst:39: WARNING: label non défini: 'convention on variable names'
|
||||||
|
/variable.rst:83: WARNING: term not in glossary: 'leading'
|
||||||
|
/variable.rst:100: WARNING: term not in glossary: 'required'
|
||||||
|
/variable.rst:102: WARNING: term not in glossary: 'leader'
|
||||||
|
/variable.rst:102: WARNING: term not in glossary: 'follower'
|
||||||
|
/variable.rst:126: WARNING: term not in glossary: 'multiple'
|
||||||
|
/variable.rst:126: WARNING: term not in glossary: 'multiple'
|
||||||
|
/variable.rst:131: WARNING: term not in glossary: 'calculation'
|
||||||
|
/variable.rst:143: WARNING: term not in glossary: 'calculation'
|
||||||
|
/variable.rst:148: WARNING: term not in glossary: 'calculation'
|
||||||
|
/variable.rst:153: WARNING: term not in glossary: 'calculation'
|
||||||
|
/dictionary.rst:9: WARNING: term not in glossary: 'templates'
|
||||||
|
/dictionary.rst:19: WARNING: term not in glossary: 'redefine'
|
||||||
|
/dictionary.rst:24: WARNING: term not in glossary: 'variable_namespace'
|
||||||
|
|
||||||
|
|
||||||
.. image:: images/logo.png
|
.. image:: images/logo.png
|
||||||
|
|
||||||
- is a `delicious cooked dish <https://fr.wikipedia.org/wiki/Rougail>`_ from the Mauritius and Reunion Islands,
|
- is a `delicious cooked dish <https://fr.wikipedia.org/wiki/Rougail>`_ from the Mauritius and Reunion Islands,
|
||||||
|
|
||||||
- it is also a `Python3 <https://www.python.org/>`_ library which enables us to conveniently load application :term:`variable`\ s in a simple `YAML <https://yaml.org/>`_ format in such a way that the end user consumer can handle them consistently (that is, against an user-defined consistency).
|
- it is also a `Python <https://www.python.org/>`_ library which enables us to conveniently load application :term:`variable`\ s in a simple `YAML <https://yaml.org/>`_ format in such a way that the end user consumer can handle them consistently (that is, against an user-defined consistency).
|
||||||
|
|
||||||
In other words, using Rougail in your application or your python libraries can tansform end user consumer defined consistency rules into highly consistent business objects.
|
In other words, using Rougail in your application or your python libraries can tansform end user consumer defined consistency rules into highly consistent business objects.
|
||||||
|
|
||||||
|
@ -27,14 +50,7 @@ Explained differently, Rougail allows you to easily implement an integration of
|
||||||
:caption: Getting started
|
:caption: Getting started
|
||||||
|
|
||||||
gettingstarted
|
gettingstarted
|
||||||
tutorial
|
tutorial/index
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
:titlesonly:
|
|
||||||
:caption: The library
|
|
||||||
|
|
||||||
library
|
|
||||||
configuration
|
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:titlesonly:
|
:titlesonly:
|
||||||
|
@ -53,6 +69,13 @@ Explained differently, Rougail allows you to easily implement an integration of
|
||||||
Value checks <check>
|
Value checks <check>
|
||||||
condition
|
condition
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:titlesonly:
|
||||||
|
:caption: The library
|
||||||
|
|
||||||
|
library
|
||||||
|
configuration
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:titlesonly:
|
:titlesonly:
|
||||||
:caption: Notes
|
:caption: Notes
|
||||||
|
|
104
docs/library.rst
|
@ -3,7 +3,8 @@
|
||||||
|
|
||||||
Rougail is a configuration management library that allows you to load variables in a simple and convenient way.
|
Rougail is a configuration management library that allows you to load variables in a simple and convenient way.
|
||||||
|
|
||||||
In the following examples, we will use a specific configuration of Rougail. You will find all the options to :doc:`customize the directories structure used <configuration>`.
|
In the following examples, we will use a specific configuration of Rougail.
|
||||||
|
You will find all the configuraiton options in :doc:`configuration`.
|
||||||
|
|
||||||
To load the configuration you must import the `RougailConfig` class and set the `dictionaries_dir` values:
|
To load the configuration you must import the `RougailConfig` class and set the `dictionaries_dir` values:
|
||||||
|
|
||||||
|
@ -13,19 +14,19 @@ To load the configuration you must import the `RougailConfig` class and set the
|
||||||
|
|
||||||
RougailConfig['dictionaries_dir'] = ['dict']
|
RougailConfig['dictionaries_dir'] = ['dict']
|
||||||
|
|
||||||
Let's convert a dictionary
|
Let's convert a structure file
|
||||||
-----------------------------
|
-------------------------------
|
||||||
|
|
||||||
As a reminder, a :term:`dictionary` is a set of instructions that will allow us to create :term:`families` and :term:`variables`.
|
As a reminder, a :term:`structure file` is a set of instructions that will allow us to create :term:`families` and :term:`variables`.
|
||||||
|
|
||||||
Let's start by creating a simple dictionary.
|
Let's start by creating a simple structure file.
|
||||||
|
|
||||||
Here is a first :file:`dict/00-base.yml` dictionary:
|
Here is a first :file:`dict/00-base.yml` structure file:
|
||||||
|
|
||||||
.. code-block:: yaml
|
.. code-block:: yaml
|
||||||
|
|
||||||
---
|
---
|
||||||
version: '1.0'
|
version: '1.1'
|
||||||
my_variable:
|
my_variable:
|
||||||
default: my_value
|
default: my_value
|
||||||
|
|
||||||
|
@ -41,15 +42,34 @@ Then, let's create the :term:`Tiramisu` objects via the following script:
|
||||||
config = rougail.get_config()
|
config = rougail.get_config()
|
||||||
print(config.value.get())
|
print(config.value.get())
|
||||||
|
|
||||||
Let's execute `script.py`:
|
.. demo:: Let's execute `script.py`:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
$ python3 script.py
|
$ python3 script.py
|
||||||
{'rougail.my_variable': 'my_value'}
|
{'rougail.my_variable': 'my_value'}
|
||||||
|
|
||||||
Let's convert an extra dictionary
|
The operator role
|
||||||
-------------------------------------
|
--------------------
|
||||||
|
|
||||||
|
|
||||||
|
The :term:`operator` role corresponds to the :term:`tiramisu` settings:
|
||||||
|
|
||||||
|
.. image:: images/tiramisu_get_set.png
|
||||||
|
|
||||||
|
.. index:: questionary
|
||||||
|
|
||||||
|
But instead of coding in the end user developer way, the opterator will prefer using the Rougail CLI interface:
|
||||||
|
|
||||||
|
.. image:: images/QuestionaryChoice.png
|
||||||
|
|
||||||
|
|
||||||
|
The Rougail CLI can output a rather complete view of the dataset:
|
||||||
|
|
||||||
|
.. image:: images/UserDataOutput.png
|
||||||
|
|
||||||
|
Let's convert an extra structure file
|
||||||
|
---------------------------------------
|
||||||
|
|
||||||
.. index:: extras
|
.. index:: extras
|
||||||
|
|
||||||
|
@ -65,12 +85,12 @@ For example, here's how to add an `example` namespace:
|
||||||
|
|
||||||
RougailConfig['extra_dictionaries']['example'] = ['extras/']
|
RougailConfig['extra_dictionaries']['example'] = ['extras/']
|
||||||
|
|
||||||
Then let's create an extra :term:`dictionary` :file:`extras/00-base.yml`:
|
Then let's create an extra :term:`structure file` :file:`extras/00-base.yml`:
|
||||||
|
|
||||||
.. code-block:: yaml
|
.. code-block:: yaml
|
||||||
:caption: the :file:`extras/00-base.yml` file content
|
:caption: the :file:`extras/00-base.yml` file content
|
||||||
---
|
---
|
||||||
version: '1.0'
|
version: '1.1'
|
||||||
my_variable_extra:
|
my_variable_extra:
|
||||||
default: my_value_extra
|
default: my_value_extra
|
||||||
|
|
||||||
|
@ -97,12 +117,12 @@ Let's execute `script.py`:
|
||||||
Let's create a custom function
|
Let's create a custom function
|
||||||
----------------------------------
|
----------------------------------
|
||||||
|
|
||||||
We create the complementary :term:`dictionary` named :file:`dict/01-function.yml` so that the `my_variable_jinja` variable is :term:`calculated`:
|
We create the complementary :term:`structure file` named :file:`dict/01-function.yml` so that the `my_variable_jinja` variable is :term:`calculated`:
|
||||||
|
|
||||||
.. code-block:: yaml
|
.. code-block:: yaml
|
||||||
|
|
||||||
---
|
---
|
||||||
version: '1.0'
|
version: '1.1'
|
||||||
my_variable_jinja:
|
my_variable_jinja:
|
||||||
type: "string"
|
type: "string"
|
||||||
default:
|
default:
|
||||||
|
@ -140,7 +160,6 @@ Let's execute `script.py`:
|
||||||
|
|
||||||
The value of the `my_variable_extra` variable is calculated, and it's value comes from the :func:`return_no` function.
|
The value of the `my_variable_extra` variable is calculated, and it's value comes from the :func:`return_no` function.
|
||||||
|
|
||||||
|
|
||||||
Create your own type
|
Create your own type
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
|
@ -170,23 +189,68 @@ Here an example to a lipogram option (in a string, we cannot use "e" character):
|
||||||
To add the new lipogram type in Rougail:
|
To add the new lipogram type in Rougail:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
>>> from rougail import Rougail, RougailConfig
|
>>> from rougail import Rougail, RougailConfig
|
||||||
>>> RougailConfig['dictionaries_dir'] = ['dict']
|
>>> RougailConfig['dictionaries_dir'] = ['dict']
|
||||||
>>> RougailConfig['custom_types']['lipogram'] = LipogramOption
|
>>> RougailConfig['custom_types']['lipogram'] = LipogramOption
|
||||||
|
|
||||||
Now, we can use lipogram type.
|
Now, we can use lipogram type.
|
||||||
Here is a :file:`dict/00-base.yml` dictionary:
|
Here is a :file:`dict/00-base.yml` structure file:
|
||||||
|
|
||||||
.. code-block:: yaml
|
.. code-block:: yaml
|
||||||
|
|
||||||
---
|
---
|
||||||
version: '1.0'
|
version: '1.1'
|
||||||
var:
|
var:
|
||||||
type: lipogram
|
type: lipogram
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
>>> rougail = Rougail()
|
>>> rougail = Rougail()
|
||||||
>>> config = rougail.get_config()
|
>>> config = rougail.get_config()
|
||||||
>>> config.option('rougail.var').value.set('blah')
|
>>> config.option('rougail.var').value.set('blah')
|
||||||
>>> config.option('rougail.var').value.set('I just want to add a quality string that has no bad characters')
|
>>> config.option('rougail.var').value.set('I just want to add a quality string that has no bad characters')
|
||||||
[...]
|
[...]
|
||||||
tiramisu.error.ValueOptionError: "I just want to add a quality string that has no bad characters" is an invalid lipogram for "var", Perec wrote a book without any "e", you could not do it in a simple sentence?
|
tiramisu.error.ValueOptionError: "I just want to add a quality string that has no bad characters" is an invalid lipogram for "var", Perec wrote a book without any "e", you could not do it in a simple sentence?
|
||||||
|
|
||||||
|
Upgrade dictionnaries to upper version
|
||||||
|
----------------------------------------
|
||||||
|
|
||||||
|
All dictionnaries has a format version number.
|
||||||
|
When a new format version is proposed, it is possible to automatically convert the files to the new version.
|
||||||
|
|
||||||
|
We create a term:`structure file` named :file:`dict/01-upgrade.yml` with version 1.0:
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
|
||||||
|
---
|
||||||
|
version: '1.1'
|
||||||
|
my_variable:
|
||||||
|
multi: true
|
||||||
|
my_dyn_family:
|
||||||
|
type: "dynamic"
|
||||||
|
variable: my_variable
|
||||||
|
a_variable:
|
||||||
|
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
>>> from rougail import RougailUpgrade, RougailConfig
|
||||||
|
>>> RougailConfig['dictionaries_dir'] = ['dict']
|
||||||
|
>>> upgrade = RougailUpgrade()
|
||||||
|
>>> upgrade.load_dictionaries('dict_converted')
|
||||||
|
|
||||||
|
The term:`structure file` named :file:`dict_converted/01-upgrade.yml` is in version 1.1:
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
|
||||||
|
version: '1.1'
|
||||||
|
my_variable:
|
||||||
|
multi: true
|
||||||
|
my_dyn_family:
|
||||||
|
type: dynamic
|
||||||
|
a_variable: null
|
||||||
|
dynamic:
|
||||||
|
type: variable
|
||||||
|
variable: my_variable
|
||||||
|
propertyerror: false
|
||||||
|
|
25
docs/readme.txt
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
Building the doc locally
|
||||||
|
============================
|
||||||
|
|
||||||
|
install
|
||||||
|
---------
|
||||||
|
|
||||||
|
First, install a python virtual environment::
|
||||||
|
|
||||||
|
python -m venv .venv
|
||||||
|
source .venv/bin/activate
|
||||||
|
|
||||||
|
Then install the sphinx libraries::
|
||||||
|
|
||||||
|
./.venv/bin/pip3 install sphinx
|
||||||
|
./.venv/bin/pip3 install sphinx_rtd_theme
|
||||||
|
./.venv/bin/pip3 install sphinx_lesson
|
||||||
|
|
||||||
|
|
||||||
|
The generatef html output is located in the `docs/build/html` subfolder,
|
||||||
|
you can modify the target or the output type in the :file:`docs/Makefile`.
|
||||||
|
|
||||||
|
scraps
|
||||||
|
---------
|
||||||
|
|
||||||
|
`variable <https://en.wikipedia.org/wiki/Variable_(computer_science)>`_
|
BIN
docs/tutorial/images/QuestionaryChoice.png
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
docs/tutorial/images/UserDataOutput.png
Normal file
After Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 123 KiB After Width: | Height: | Size: 123 KiB |
BIN
docs/tutorial/images/firefox_01.png
Normal file
After Width: | Height: | Size: 119 KiB |
Before Width: | Height: | Size: 154 KiB After Width: | Height: | Size: 154 KiB |
BIN
docs/tutorial/images/integrator.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
docs/tutorial/images/operator.png
Normal file
After Width: | Height: | Size: 7 KiB |
46
docs/tutorial/index.rst
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
Tutorial with a real world sample
|
||||||
|
=====================================
|
||||||
|
|
||||||
|
Here is a fairly complete tutorial, this is a use case that comes from the real world.
|
||||||
|
At the end of the tutorial you will have a good understanding of rougail.
|
||||||
|
|
||||||
|
.. objectives:: Configuring (the setting of) your favorite web browser
|
||||||
|
|
||||||
|
This tutorial will show you an example of Rougail use based on the
|
||||||
|
*how to set a proxy* in the `Mozilla Firefox <https://www.mozilla.org/en-US/firefox/new/>`_ browser
|
||||||
|
use case.
|
||||||
|
|
||||||
|
More precisely, this tutorial aims at reproducing :term:`variable`\ s behind this Mozilla Firefox settings page:
|
||||||
|
|
||||||
|
.. image:: images/firefox.png
|
||||||
|
|
||||||
|
We'll call the variables **configuration options** since that's what the variables represent in this case.
|
||||||
|
|
||||||
|
.. attention:: We are not coding a firefox plugin here.
|
||||||
|
We are just going to handle some of the firefox configuration settings
|
||||||
|
with Rougail.
|
||||||
|
|
||||||
|
Presentation of the firefox option configuration variables
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
Let's dive into this **configuration options validation** use case.
|
||||||
|
The values entered by the user have to be
|
||||||
|
|
||||||
|
- validated
|
||||||
|
- consitent
|
||||||
|
|
||||||
|
At first glance we can see that we have a selection of five configuration options values that we need to fill in (they are highlighted here in this screenshot):
|
||||||
|
|
||||||
|
.. image:: images/firefox_01.png
|
||||||
|
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:titlesonly:
|
||||||
|
:caption: The firefox tutorial steps
|
||||||
|
:hidden:
|
||||||
|
|
||||||
|
preliminary
|
||||||
|
proxymode
|
||||||
|
disabled
|
||||||
|
tutorial
|
||||||
|
|
346
docs/tutorial/preliminary.rst
Normal file
|
@ -0,0 +1,346 @@
|
||||||
|
Preliminaries
|
||||||
|
================
|
||||||
|
|
||||||
|
.. objectives:: Objectives
|
||||||
|
|
||||||
|
We will learn how to:
|
||||||
|
|
||||||
|
- create a Rougail :term:`structure description file <structure file>`
|
||||||
|
- define a Rougail :term:`structure description file <structure file>` format version
|
||||||
|
- define a Rougail :term:`variable` and set its :term:`value`
|
||||||
|
|
||||||
|
.. prerequisites:: Prerequisites
|
||||||
|
|
||||||
|
We assume that Rougail's library is :ref:`already installed <installation>` on your computer (or in a virtual environment).
|
||||||
|
|
||||||
|
.. type-along:: an empty structure file
|
||||||
|
|
||||||
|
An empty structure description file
|
||||||
|
|
||||||
|
.. exercise:: The folder structure
|
||||||
|
|
||||||
|
Here is the tree structure we want to have::
|
||||||
|
|
||||||
|
workplace
|
||||||
|
├── firefox
|
||||||
|
│ └── struct.yml
|
||||||
|
|
||||||
|
- Let's make a :file:`workplace` directory, with a :file:`firefox` subfolder.
|
||||||
|
- First, we wil make a :term:`structure file <structure file>`, so let's create a :file:`struct.yml` file
|
||||||
|
located in the :file:`firefox` subfolder.
|
||||||
|
|
||||||
|
The structure file
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
This is an empty Rougail dictionnary
|
||||||
|
|
||||||
|
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_000/firefox/00-proxy.yml
|
||||||
|
:language: yaml
|
||||||
|
:caption: An empty rougail dictionnary file with the version number only
|
||||||
|
:name: RougailStructVersion
|
||||||
|
|
||||||
|
..
|
||||||
|
---
|
||||||
|
version: 1.1
|
||||||
|
|
||||||
|
.. type-along:: a first variable
|
||||||
|
|
||||||
|
Let's put a variable in the Rougail :term:`structure description file <structure file>`
|
||||||
|
|
||||||
|
Defining a variable and its default value
|
||||||
|
----------------------------------------------
|
||||||
|
|
||||||
|
This is a first Rougail variable in a Rougail dictionnary.
|
||||||
|
|
||||||
|
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_010/firefox/00-proxy.yml
|
||||||
|
:language: yaml
|
||||||
|
:caption: A Rougail dictionnary file with only one variable
|
||||||
|
:name: RougailDictionaryFirstVariableName
|
||||||
|
|
||||||
|
..
|
||||||
|
---
|
||||||
|
proxy_mode:
|
||||||
|
|
||||||
|
- Then if we run the Rougail CLI utility command
|
||||||
|
|
||||||
|
.. code-block:: text
|
||||||
|
:class: terminal
|
||||||
|
|
||||||
|
rougail -v 1.1 -m firefox/
|
||||||
|
|
||||||
|
we will actually have an error:
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_010/config/01/output_ro.html
|
||||||
|
:class: error-box
|
||||||
|
|
||||||
|
..
|
||||||
|
🛑 ERRORS
|
||||||
|
┣━━ The following variables are mandatory but have no value:
|
||||||
|
┗━━ - proxy_mode
|
||||||
|
|
||||||
|
Because this variable is :term:`mandatory` and needs to be set **but** there's no value yet.
|
||||||
|
|
||||||
|
So we can therefore see this consequence:
|
||||||
|
|
||||||
|
.. important:: Once defined, an option configuration :term:`value` is :term:`mandatory`.
|
||||||
|
|
||||||
|
Rougail waits for the `proxy_mode` configuration option's value to be set.
|
||||||
|
|
||||||
|
.. seealso:: To go further, have a look at the :tiramisu:`mandatory option <glossary.html#term-mandatory-option>` Tiramisu's definition.
|
||||||
|
|
||||||
|
.. glossary::
|
||||||
|
|
||||||
|
mandatory
|
||||||
|
|
||||||
|
A variable is mandatory when a value is required, that is, `None` is not an option.
|
||||||
|
It **must** have a defined value.
|
||||||
|
|
||||||
|
Then we just add a variable's description, which is a good practice.
|
||||||
|
|
||||||
|
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_011/firefox/00-proxy.yml
|
||||||
|
:language: yaml
|
||||||
|
:caption: A Rougail dictionnary file with a variable and a description
|
||||||
|
:name: RougailStructFirstVariableDescription
|
||||||
|
|
||||||
|
..
|
||||||
|
---
|
||||||
|
proxy_mode:
|
||||||
|
description: Configure Proxy Access to the Internet
|
||||||
|
|
||||||
|
We need a **default value**.
|
||||||
|
|
||||||
|
.. glossary::
|
||||||
|
|
||||||
|
default value
|
||||||
|
|
||||||
|
A default value is a variable value that is predefined, that is, this value is placed
|
||||||
|
right in the structure file.
|
||||||
|
|
||||||
|
So let's define a variable with a description -- **and a default value**
|
||||||
|
|
||||||
|
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/commit/a1969abccd5e10872d4eb901a26fa16007c9fb1d/firefox/00-proxy.yml
|
||||||
|
:language: yaml
|
||||||
|
:caption: A rougail dictionnary file with a default value for the variable
|
||||||
|
:name: RougailDictionaryVariableDefault
|
||||||
|
|
||||||
|
..
|
||||||
|
---
|
||||||
|
proxy_mode:
|
||||||
|
description: Configure Proxy Access to the Internet
|
||||||
|
default: No proxy
|
||||||
|
|
||||||
|
|
||||||
|
.. type-along:: how to set a value
|
||||||
|
|
||||||
|
A default value has been set, great. Now how do I assign a value to a variable?
|
||||||
|
|
||||||
|
How to set a value
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
So far we have only talked about the one that writes the structure files. It is called the integrator.
|
||||||
|
|
||||||
|
.. glossary::
|
||||||
|
|
||||||
|
integrator
|
||||||
|
|
||||||
|
An integrator in the Rougail logic is the person who writes the :term:`structure files <structure file>`\ .
|
||||||
|
He has the responsibilité of the integration process, that is,
|
||||||
|
defines the variables and the relationship between them, the variables that are allowed
|
||||||
|
(or not) to be set, and so on. His responsabilites are the **structuration** and the **consistency**
|
||||||
|
of the variables.
|
||||||
|
|
||||||
|
Now we will talk about the one that defines the values. It is called the operator.
|
||||||
|
|
||||||
|
.. glossary::
|
||||||
|
|
||||||
|
operator
|
||||||
|
|
||||||
|
An operator ih the Rougail logic is the person who gives :term:`value`\ s to the pre-defined variables,
|
||||||
|
his responsabilities are to set variable values correctly.
|
||||||
|
|
||||||
|
The user :term:`value`\ s, that is the values that have been set by the operator, are of course type validated by the :term:`structure file definition <structure file>`.
|
||||||
|
|
||||||
|
Values are mandatory
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
It is the operator's responsibility to set configuration options values.
|
||||||
|
He does not work with the structure file,
|
||||||
|
he is responsible for other files, called the configuration files.
|
||||||
|
|
||||||
|
.. glossary::
|
||||||
|
|
||||||
|
configuration file
|
||||||
|
|
||||||
|
A configuration file is a file where only the values of the configuration options are assigned. The structure, the consistency of the variables between them is not the responsibility of these files (but of the structure files).
|
||||||
|
|
||||||
|
.. exercise:: Folder structure update
|
||||||
|
|
||||||
|
Now we add a :file:`config/config.yml` file in our project::
|
||||||
|
|
||||||
|
workplace
|
||||||
|
├── firefox
|
||||||
|
│ ├── struct.yml
|
||||||
|
└── config
|
||||||
|
└── config.yml
|
||||||
|
|
||||||
|
.. type-along:: how to set a default value in a configuration file
|
||||||
|
|
||||||
|
So for example if the integrator has not set any default value in his structure file,
|
||||||
|
the operator can do it like this:
|
||||||
|
|
||||||
|
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_011/config/02/config.yaml
|
||||||
|
:language: yaml
|
||||||
|
:caption: The Rougail configuration file :file:`config/config.yml`, with a default value set.
|
||||||
|
:name: RougailConfigDefaultValue
|
||||||
|
|
||||||
|
..
|
||||||
|
---
|
||||||
|
proxy_mode: No proxy
|
||||||
|
|
||||||
|
With the rougail CLI the operator has to add the `-u file -ff config/config.yml` options:
|
||||||
|
|
||||||
|
.. code-block:: text
|
||||||
|
:class: terminal
|
||||||
|
:caption: A rougail Command Line Utility call with the :file:`config/config.yml` Rougail configuration file
|
||||||
|
|
||||||
|
rougail -v 1.1 -m firefox -u file -ff config/01/config.yaml
|
||||||
|
|
||||||
|
Let us clarify this essential point:
|
||||||
|
|
||||||
|
- the integrator works on dictionaries
|
||||||
|
- the operator works on configuration files
|
||||||
|
|
||||||
|
.. note:: Most of the time, the integrator and the operator are one and the same person, here we are talking about roles and not necessarily about people.
|
||||||
|
|
||||||
|
Variable's type
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
If the `type` attribute is not set, Rougail infers a `string` type for the `proxy_mode` configuration option variable type as defined in the structure file.
|
||||||
|
|
||||||
|
If the operator sets an option value for example with the `number` type, like this:
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
|
||||||
|
---
|
||||||
|
example_var:
|
||||||
|
description: Configure Proxy Access to the Internet
|
||||||
|
type: number
|
||||||
|
|
||||||
|
Then Rougail will expect a `int` or a `float` as a value for the `example_var` variable.
|
||||||
|
In our firefox use case, the real type of the `proxy_mode` variable will be now set as a `choice` type:
|
||||||
|
|
||||||
|
.. type-along:: Defining a choice type
|
||||||
|
|
||||||
|
A choice type variable is a variable where the content is constrained by a list
|
||||||
|
|
||||||
|
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_013/firefox/00-proxy.yml
|
||||||
|
:linenos:
|
||||||
|
:language: yaml
|
||||||
|
:caption: The real :file:`firefox/proxy.yml` Rougail dictionnary file with a choice type
|
||||||
|
:name: RougailDictionaryChoiceType
|
||||||
|
|
||||||
|
..
|
||||||
|
---
|
||||||
|
proxy_mode:
|
||||||
|
description: Configure Proxy Access to the Internet
|
||||||
|
type: choice
|
||||||
|
choices:
|
||||||
|
- No proxy
|
||||||
|
- Auto-detect proxy settings for this network
|
||||||
|
- Use system proxy settings
|
||||||
|
- Manual proxy configuration
|
||||||
|
- Automatic proxy configuration URL
|
||||||
|
default: No proxy
|
||||||
|
|
||||||
|
- Then if we run the Rougail CLI
|
||||||
|
|
||||||
|
.. code-block:: text
|
||||||
|
:class: terminal
|
||||||
|
|
||||||
|
rougail -v 1.1 -m firefox/
|
||||||
|
|
||||||
|
We will have an output like this:
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/commit/v1.1_013/config/01/output_ro.html
|
||||||
|
:class: output
|
||||||
|
|
||||||
|
..
|
||||||
|
<pre>╭────────────────────────── Caption ──────────────────────────╮
|
||||||
|
│ Variable <span style="color: #ffd700">Default value</span> │
|
||||||
|
│ <span style="color: #5c5cff">Undocumented variable</span> Modified value │
|
||||||
|
│ <span style="color: #ff0000">Undocumented but modified variable</span> (<span style="color: #00aa00">Original default value</span>) │
|
||||||
|
│ <span style="color: #ffaf00">Unmodifiable variable</span> │
|
||||||
|
╰─────────────────────────────────────────────────────────────╯
|
||||||
|
Variables:
|
||||||
|
<span style="color: #5c5cff">┗━━ </span>📓 proxy_mode: <span style="color: #ffd700">No proxy</span>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
The `proxy_mode` variable here, implicitely requires a value. It is a :term:`mandatory` variable.
|
||||||
|
|
||||||
|
As we set the `proxy_mode` variable as `No proxy` by default, we actually have specified a value, and the value appears in yellow, which means : "set by default".
|
||||||
|
|
||||||
|
Container type
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
We say that the `proxy_mode` variable is *constrained* (by the `choice` type).
|
||||||
|
|
||||||
|
We have the list of the possible (authorized) values:
|
||||||
|
|
||||||
|
- `No proxy`
|
||||||
|
- `Auto-detect proxy settings for this network`
|
||||||
|
- `Use system proxy settings`
|
||||||
|
- `Manual proxy configuration`
|
||||||
|
- `Automatic proxy configuration URL`
|
||||||
|
|
||||||
|
.. glossary::
|
||||||
|
|
||||||
|
choice type
|
||||||
|
|
||||||
|
The `proxy_mode` setting is "choice" (`type: choice`) means that
|
||||||
|
there is a list of available values that can be selected.
|
||||||
|
|
||||||
|
.. keypoints:: Key points progress
|
||||||
|
|
||||||
|
Indeed, in the Firefox configuration, it is possible to define several configuration modes, from no proxy at all (`no proxy`) to a kind of automatic configuration mode from a file (`set up proxy configuration from a file`).
|
||||||
|
|
||||||
|
|
||||||
|
**Keywords**
|
||||||
|
|
||||||
|
- :term:`structure file <structure file>`: structure description file
|
||||||
|
- :term:`variable`: an option's name which has a value
|
||||||
|
- a variable's description
|
||||||
|
- a variable's type
|
||||||
|
- a variable's default value
|
||||||
|
- the :term:`integrator` and :term:`operator` roles
|
||||||
|
- a mandatory value
|
||||||
|
- a choice type
|
||||||
|
|
||||||
|
**Progress**
|
||||||
|
|
||||||
|
To sum up, we have arrived at this point in writing a structure file like this:
|
||||||
|
|
||||||
|
**Structure description file**
|
||||||
|
|
||||||
|
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_013/firefox/00-proxy.yml
|
||||||
|
:linenos:
|
||||||
|
:language: yaml
|
||||||
|
:caption: A Rougail dictionnary file with a variable named `proxy_mode`, with a default value.
|
||||||
|
|
||||||
|
..
|
||||||
|
|
||||||
|
.. raw:: text
|
||||||
|
|
||||||
|
---
|
||||||
|
proxy_mode:
|
||||||
|
description: Configure Proxy Access to the Internet
|
||||||
|
type: choice
|
||||||
|
choices:
|
||||||
|
- No proxy
|
||||||
|
- Auto-detect proxy settings for this network
|
||||||
|
- Use system proxy settings
|
||||||
|
- Manual proxy configuration
|
||||||
|
- Automatic proxy configuration URL
|
||||||
|
default: No proxy
|
||||||
|
|
326
docs/tutorial/proxymode.rst
Normal file
|
@ -0,0 +1,326 @@
|
||||||
|
The `manual` family
|
||||||
|
====================
|
||||||
|
|
||||||
|
.. objectives:: Objectives
|
||||||
|
|
||||||
|
We will learn how to:
|
||||||
|
|
||||||
|
- create a :term:`family`
|
||||||
|
- gather :term:`variable`\ s into a :term:`family`
|
||||||
|
- make a variable within a variable, which turns this variable container into being a family
|
||||||
|
|
||||||
|
.. prerequisites:: Reminders
|
||||||
|
|
||||||
|
- We have an idea of what a :term:`structure description file<structure file>` is
|
||||||
|
- We have a folder named :file:`firefox` and we are putting all the structure description files into it
|
||||||
|
|
||||||
|
|
||||||
|
.. type-along:: So we have this choice type variable in the structure file
|
||||||
|
|
||||||
|
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_013/firefox/00-proxy.yml
|
||||||
|
:linenos:
|
||||||
|
:language: yaml
|
||||||
|
:caption: The `proxy_mode` choice type variable in the :file:`firefox/00-proxy.yml` structure file
|
||||||
|
|
||||||
|
..
|
||||||
|
---
|
||||||
|
proxy_mode:
|
||||||
|
description: Configure Proxy Access to the Internet
|
||||||
|
type: choice
|
||||||
|
choices:
|
||||||
|
- No proxy
|
||||||
|
- Auto-detect proxy settings for this network
|
||||||
|
- Use system proxy settings
|
||||||
|
- Manual proxy configuration
|
||||||
|
- Automatic proxy configuration URL
|
||||||
|
default: No proxy
|
||||||
|
|
||||||
|
.. We're gonna put it in a :term:`family`.
|
||||||
|
|
||||||
|
.. note:: The variables, families, etc. will be created in several files for educational purposes.
|
||||||
|
Here we made a :file:`firefox/00-proxy.yml` structure file and we're gonna make
|
||||||
|
a new structure file named :file:`firefox/10-manual.yml`
|
||||||
|
We could of course have put everything in one file, we decided to separate the files for reasons of ordering and clarity
|
||||||
|
|
||||||
|
So we have now a `proxy_mode` variable.
|
||||||
|
|
||||||
|
.. confval:: proxy_mode
|
||||||
|
:type: `choice`
|
||||||
|
:default: No proxy
|
||||||
|
|
||||||
|
This is a setting that controls the proxy's type
|
||||||
|
|
||||||
|
A family named `manual`
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
Let's create a family named `manual`
|
||||||
|
|
||||||
|
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_020/firefox/10-manual.yml
|
||||||
|
:language: yaml
|
||||||
|
:caption: A family structure file description named `manual` in a :file:`firefox/10-manual.yml` file
|
||||||
|
:name: RougailManualFamily
|
||||||
|
|
||||||
|
..
|
||||||
|
---
|
||||||
|
manual:
|
||||||
|
description: Manual proxy configuration
|
||||||
|
type: family
|
||||||
|
|
||||||
|
We can see that we have defined a :term:`family` here, and this family is *empty*
|
||||||
|
(that is the family which is a container variable contains no variable yet).
|
||||||
|
|
||||||
|
.. admonition:: If a family is empty
|
||||||
|
|
||||||
|
We need to specify the :term:`family` type here because if we don't,
|
||||||
|
the Rougail's type engine will infer it by default as a :term:`variable`.
|
||||||
|
|
||||||
|
It's because we don't have set any :term:`variable` inside. If we have a variable inside of a family,
|
||||||
|
we make a YAML block (that is, we indent) and the Rougail's type inference engine will implicitely infer it as a family.
|
||||||
|
|
||||||
|
.. type-along:: A family inside a family
|
||||||
|
|
||||||
|
Creating a family hierarchy is very easy, here is an example:
|
||||||
|
|
||||||
|
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_021/firefox/10-manual.yml
|
||||||
|
:language: yaml
|
||||||
|
:caption: A rougail structure description file with a hierarchy.
|
||||||
|
:name: RougailFirstFamilyHierarchy
|
||||||
|
|
||||||
|
..
|
||||||
|
---
|
||||||
|
manual:
|
||||||
|
description: Manual proxy configuration
|
||||||
|
type: family
|
||||||
|
|
||||||
|
http_proxy:
|
||||||
|
description: HTTP Proxy
|
||||||
|
type: family
|
||||||
|
|
||||||
|
Here the `http_proxy` family lives inside the `manual` family.
|
||||||
|
|
||||||
|
Putting a variable inside of a family or a sub family
|
||||||
|
----------------------------------------------------------
|
||||||
|
|
||||||
|
Let's create a variable in its family.
|
||||||
|
The type of this variable is a `choice` type:
|
||||||
|
|
||||||
|
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_024/firefox/10-manual.yml
|
||||||
|
:language: yaml
|
||||||
|
:caption: An `address` variable in the `http_proxy` family
|
||||||
|
:name: RougailVariableInSubFamily
|
||||||
|
|
||||||
|
..
|
||||||
|
---
|
||||||
|
manual:
|
||||||
|
description: Manual proxy configuration
|
||||||
|
type: family
|
||||||
|
|
||||||
|
http_proxy:
|
||||||
|
description: HTTP Proxy
|
||||||
|
type: family
|
||||||
|
|
||||||
|
address:
|
||||||
|
description: HTTP address
|
||||||
|
type: domainname
|
||||||
|
|
||||||
|
The :term:`operator` can now then set :term:`a value <value>` to the :confval:`address` variable
|
||||||
|
|
||||||
|
So we have now an `address` variable.
|
||||||
|
|
||||||
|
.. confval:: address
|
||||||
|
:type: `domainname`
|
||||||
|
:default: None
|
||||||
|
|
||||||
|
This is the HTTP address of the proxy
|
||||||
|
|
||||||
|
.. note:: We encountered here a new type of variable there: the `domainname` type
|
||||||
|
|
||||||
|
|
||||||
|
Assigning a user value
|
||||||
|
------------------------------------
|
||||||
|
|
||||||
|
Now we need to set a value for the :confval:`address` variable,
|
||||||
|
otherwise we will get an error if we try to access this variable:
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_024/config/01/output_rw.html
|
||||||
|
:class: error-box
|
||||||
|
|
||||||
|
..
|
||||||
|
<pre>🛑 ERRORS
|
||||||
|
<span style="color: #ff0000">┣━━ </span>The following variables are mandatory but have no value:
|
||||||
|
<span style="color: #ff0000">┗━━ </span> - manual.http_proxy.address (HTTP address)
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
Because this variable is :term:`mandatory`.
|
||||||
|
|
||||||
|
.. type-along:: user files are where the user values lives
|
||||||
|
|
||||||
|
And we need to set the values in separate files too, called `user files`.
|
||||||
|
|
||||||
|
.. glossary::
|
||||||
|
|
||||||
|
user file
|
||||||
|
|
||||||
|
A user file is a file where users can set values, called user values --
|
||||||
|
that is variable's values that have been set by an :term:`operator`\ .
|
||||||
|
|
||||||
|
Let's set user values in user files
|
||||||
|
|
||||||
|
Here is a user file sample:
|
||||||
|
|
||||||
|
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_024/config/03/config.yaml
|
||||||
|
:language: yaml
|
||||||
|
:caption: A user file named :file:`config/config.yaml` with a value set for the `address` variable
|
||||||
|
:name: RougailAddresseVariableUserValue
|
||||||
|
|
||||||
|
..
|
||||||
|
---
|
||||||
|
proxy_mode: Manual proxy configuration
|
||||||
|
manual:
|
||||||
|
http_proxy:
|
||||||
|
address: example.net
|
||||||
|
|
||||||
|
.. glossary::
|
||||||
|
|
||||||
|
configuration
|
||||||
|
|
||||||
|
We call configuration the whole system structure and user values,
|
||||||
|
and when we speak of consistency, it is in relation to this whole set.
|
||||||
|
|
||||||
|
Let's validate the consitency of the configuration:
|
||||||
|
|
||||||
|
.. code-block:: text
|
||||||
|
:class: terminal
|
||||||
|
|
||||||
|
rougail -v 1.1 -m firefox/ -u file -ff config/config.yaml
|
||||||
|
|
||||||
|
Everything is OK:
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_024/config/03/output_rw.html
|
||||||
|
:class: output
|
||||||
|
|
||||||
|
..
|
||||||
|
<pre>╭────────────────────────── Caption ──────────────────────────╮
|
||||||
|
│ Variable <span style="color: #ffd700">Default value</span> │
|
||||||
|
│ <span style="color: #5c5cff">Undocumented variable</span> Modified value │
|
||||||
|
│ <span style="color: #ff0000">Undocumented but modified variable</span> (<span style="color: #00aa00">Original default value</span>) │
|
||||||
|
╰─────────────────────────────────────────────────────────────╯
|
||||||
|
Variables:
|
||||||
|
<span style="color: #5c5cff">┣━━ </span>📓 proxy_mode: Manual proxy configuration (<span style="color: #00aa00">No proxy</span>)
|
||||||
|
<span style="color: #5c5cff">┗━━ </span>📂 manual
|
||||||
|
<span style="color: #5c5cff"> </span><span style="color: #5c5cff">┗━━ </span>📂 http_proxy
|
||||||
|
<span style="color: #5c5cff"> </span><span style="color: #5c5cff"> </span><span style="color: #5c5cff">┗━━ </span>📓 address: example.net
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
- the `proxy_mode` value is in green because its value is set by default
|
||||||
|
- the `address` value is in black because its value has been set by a user
|
||||||
|
|
||||||
|
Variables can have parameters
|
||||||
|
---------------------------------
|
||||||
|
|
||||||
|
.. questions:: Question
|
||||||
|
|
||||||
|
**question**: Does our `address` domain name variable accepts IP addresses ?
|
||||||
|
|
||||||
|
**answer**: Well it depends.
|
||||||
|
|
||||||
|
We need to specify whether our variable accepts to be filled using an IP or a domain name only.
|
||||||
|
This is where the ability to parameterize our variable comes in.
|
||||||
|
|
||||||
|
..
|
||||||
|
---
|
||||||
|
manual:
|
||||||
|
description: Manual proxy configuration
|
||||||
|
|
||||||
|
http_proxy:
|
||||||
|
description: HTTP Proxy
|
||||||
|
|
||||||
|
address:
|
||||||
|
description: HTTP address
|
||||||
|
type: domainname
|
||||||
|
params:
|
||||||
|
allow_ip: true
|
||||||
|
|
||||||
|
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_025/firefox/10-manual.yml
|
||||||
|
:language: yaml
|
||||||
|
:caption: The `address` has a parameter set in the :file:`firefox/10-manual.yml` structure file
|
||||||
|
:name: RougailAddressParameter
|
||||||
|
:linenos:
|
||||||
|
..
|
||||||
|
---
|
||||||
|
manual:
|
||||||
|
description: Manual proxy configuration
|
||||||
|
|
||||||
|
http_proxy:
|
||||||
|
description: HTTP Proxy
|
||||||
|
|
||||||
|
address:
|
||||||
|
description: HTTP address
|
||||||
|
type: domainname
|
||||||
|
params:
|
||||||
|
allow_ip: true
|
||||||
|
|
||||||
|
We can see line 11 and 12 that the params allow the domain name `address` variable to be set
|
||||||
|
with IPs.
|
||||||
|
|
||||||
|
.. type-along:: One more variable
|
||||||
|
|
||||||
|
Let's create a `port` variable in the `http_proxy` family:
|
||||||
|
|
||||||
|
.. confval:: port
|
||||||
|
:type: `port`
|
||||||
|
:default: 8080
|
||||||
|
|
||||||
|
The HTTP Port
|
||||||
|
|
||||||
|
Here is the new :file:`firefox/10-manual.yml` structure file:
|
||||||
|
|
||||||
|
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_026/firefox/10-manual.yml
|
||||||
|
:language: yaml
|
||||||
|
:caption: A rougail structure description file with a hierarchy.
|
||||||
|
:name: RogailPortVariable
|
||||||
|
:linenos:
|
||||||
|
|
||||||
|
..
|
||||||
|
---
|
||||||
|
manual:
|
||||||
|
description: Manual proxy configuration
|
||||||
|
|
||||||
|
http_proxy:
|
||||||
|
description: HTTP Proxy
|
||||||
|
|
||||||
|
address:
|
||||||
|
description: HTTP address
|
||||||
|
type: domainname
|
||||||
|
params:
|
||||||
|
allow_ip: true
|
||||||
|
|
||||||
|
port:
|
||||||
|
description: HTTP Port
|
||||||
|
type: port
|
||||||
|
default: 8080
|
||||||
|
|
||||||
|
Take a look at lines 14 to 17.
|
||||||
|
|
||||||
|
.. glossary::
|
||||||
|
|
||||||
|
parameter
|
||||||
|
|
||||||
|
A parameter is a property of a variable that can refine its behavior
|
||||||
|
|
||||||
|
.. keypoints:: Key points progress
|
||||||
|
|
||||||
|
**Keywords**
|
||||||
|
|
||||||
|
- we know how to define :term:`variable`\ s inside of a family
|
||||||
|
- we now know what a :term:`mandatory` variable is
|
||||||
|
- we kwow how to set a variable's user value (in a :term:`user file`)
|
||||||
|
- we have the big picture : the :term:`configuration`, which is (the structure files + the user files)
|
||||||
|
- we can add :term:`parameter`\ s to variables to refine their behavior
|
||||||
|
|
||||||
|
**Progress**
|
||||||
|
|
||||||
|
- we have a :term:`family` named `manual` and a sub family named `http_proxy`
|
||||||
|
- And we have now two variables: :confval:`proxy_mode` and :confval:`address`.
|
|
@ -1,119 +1,22 @@
|
||||||
Tutorial: a real world sample
|
The Firefox proxy: the manual configuration
|
||||||
==============================
|
=============================================
|
||||||
|
|
||||||
.. demo:: Demonstration : configuring (the setting of) your favorite web browser
|
|
||||||
|
|
||||||
This tutorial shows to you an example of Rougail use on
|
|
||||||
how to set a proxy in the `Mozilla Firefox <https://www.mozilla.org/en-US/firefox/new/>`_ browser.
|
|
||||||
|
|
||||||
More precisely, this tutorial aims at reproducing this Mozilla Firefox settings page:
|
|
||||||
|
|
||||||
.. image:: images/firefox.png
|
|
||||||
|
|
||||||
.. important:: Here we are in the configuration validation use case,
|
|
||||||
that is the values entered by the user have to be validated.
|
|
||||||
It's a common use case, but not the only one.
|
|
||||||
|
|
||||||
Let's explain this use case.
|
|
||||||
|
|
||||||
The Firefox proxy configuration
|
|
||||||
-------------------------------------------
|
|
||||||
|
|
||||||
The `proxy` family
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
Let's create our first :term:`dictionary`.
|
|
||||||
|
|
||||||
.. prerequisites:: Let's create a folder named `dict` and a dictionary file inside
|
|
||||||
|
|
||||||
We will put our dictionary files in this folder.
|
|
||||||
|
|
||||||
Then let's put our first dictionary file in this folder, named :file:`00-proxy.yml`
|
|
||||||
|
|
||||||
.. code-block:: yaml
|
|
||||||
:caption: the :file:`00-proxy.yml` file
|
|
||||||
:linenos:
|
|
||||||
|
|
||||||
---
|
|
||||||
version: '1.0'
|
|
||||||
proxy:
|
|
||||||
description: Proxy configuration in order to have access to the internet
|
|
||||||
type: family
|
|
||||||
|
|
||||||
We can see that we have defined a :term:`family` here, and this family is *empty*
|
|
||||||
(that is, the family container contains no variable yet).
|
|
||||||
|
|
||||||
.. admonition:: If a family is empty
|
|
||||||
|
|
||||||
We need to specify the :term:`family` type (line 5) here because if we don't,
|
|
||||||
the Rougail's type engine will infer it by default as a :term:`variable`.
|
|
||||||
|
|
||||||
It's because we don't have set any :term:`variable` inside.
|
|
||||||
|
|
||||||
|
|
||||||
.. note:: The variables will be created in several files for educational purposes.
|
.. objectives:: Objectives
|
||||||
Obviously all the variables can be put in the same file.
|
|
||||||
|
We will learn how to: FIXME
|
||||||
|
|
||||||
|
|
||||||
The proxy's configuration type
|
.. prerequisites:: Reminders
|
||||||
----------------------------------
|
|
||||||
|
|
||||||
In the Firefox configuration, it is possible to define several configuration modes,
|
FIXME
|
||||||
from no proxy at all (`no proxy`) to a kind of automatic configuration mode from a file (`set up proxy configuration from a file`).
|
|
||||||
|
|
||||||
We're gonna create a first variable in this family with "Proxy mode" as the description.
|
|
||||||
Let's create a second :file:`dict/01-proxy_mode.yml` file.
|
|
||||||
|
|
||||||
.. code-block:: yaml
|
|
||||||
:caption: the :file:`001-proxy_mode.yml` file
|
|
||||||
:linenos:
|
|
||||||
|
|
||||||
---
|
|
||||||
version: '1.0'
|
|
||||||
proxy:
|
|
||||||
proxy_mode:
|
|
||||||
description: Proxy mode
|
|
||||||
type: choice
|
|
||||||
choices:
|
|
||||||
- No proxy
|
|
||||||
- Auto-detect proxy settings for this network
|
|
||||||
- Use system proxy settings
|
|
||||||
- Manual proxy configuration
|
|
||||||
- Automatic proxy configuration URL
|
|
||||||
default: No proxy
|
|
||||||
|
|
||||||
The `proxy_mode` variable requires a value (that is, `None` is not an option).
|
|
||||||
It shall have a value, but what if the user *does not* specify any value?
|
|
||||||
There is line 13, a possibility of setting a default value, wich is `No proxy` as the default.
|
|
||||||
|
|
||||||
The `proxy_mode` setting is "choice" (`type: choice`) means that
|
|
||||||
there is a list of available values that can be selected.
|
|
||||||
We say that the `proxy_mode` variable is *constrained* (by choices).
|
|
||||||
|
|
||||||
Line 8 to 12, we have the list of the possible (authorized) values:
|
|
||||||
|
|
||||||
- No proxy
|
|
||||||
- Auto-detect proxy settings for this network
|
|
||||||
- Use system proxy settings
|
|
||||||
- Manual proxy configuration
|
|
||||||
- Automatic proxy configuration URL
|
|
||||||
|
|
||||||
Now let's test our first two dictionaries:
|
|
||||||
|
|
||||||
|
|
||||||
>>> from rougail import Rougail, RougailConfig
|
|
||||||
>>> from pprint import pprint
|
|
||||||
>>> RougailConfig['dictionaries_dir'] = ['dict']
|
|
||||||
>>> rougail = Rougail()
|
|
||||||
>>> config = rougail.get_config()
|
|
||||||
>>> config.property.read_only()
|
|
||||||
>>> pprint(config.value.get(), sort_dicts=False)
|
|
||||||
{'rougail.proxy.proxy_mode': 'No proxy'}
|
|
||||||
|
|
||||||
The manual mode
|
The manual mode
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
.. questions:: OK then. What happens when you select the "Manual proxy configuration"?
|
.. questions:: Question
|
||||||
|
|
||||||
|
**question**: OK then. What happens when you select the "Manual proxy configuration"?
|
||||||
|
|
||||||
A good configuration design is to place all the proxy's manual configuration in a :term:`family`.
|
A good configuration design is to place all the proxy's manual configuration in a :term:`family`.
|
||||||
Let's create the :file:`dict/02-proxy_manual.yml` dictionary:
|
Let's create the :file:`dict/02-proxy_manual.yml` dictionary:
|
||||||
|
@ -122,7 +25,7 @@ Let's create the :file:`dict/02-proxy_manual.yml` dictionary:
|
||||||
:caption: the the :file:`dict/02-proxy_manual.yml` file
|
:caption: the the :file:`dict/02-proxy_manual.yml` file
|
||||||
|
|
||||||
---
|
---
|
||||||
version: '1.0'
|
version: '1.1'
|
||||||
proxy:
|
proxy:
|
||||||
manual:
|
manual:
|
||||||
description: Manual proxy configuration
|
description: Manual proxy configuration
|
||||||
|
@ -170,7 +73,7 @@ Let's create the :file:`dict/03-proxy_manual_http_proxy.yml` dictionary:
|
||||||
:linenos:
|
:linenos:
|
||||||
|
|
||||||
---
|
---
|
||||||
version: '1.0'
|
version: '1.1'
|
||||||
proxy:
|
proxy:
|
||||||
manual:
|
manual:
|
||||||
http_proxy:
|
http_proxy:
|
||||||
|
@ -195,7 +98,7 @@ We then want to offer the user the possibility of providing the same proxy for t
|
||||||
.. code-block:: yaml
|
.. code-block:: yaml
|
||||||
:caption: the :file:`dict/04-proxy_manual_http_use_for_https.yml` file
|
:caption: the :file:`dict/04-proxy_manual_http_use_for_https.yml` file
|
||||||
|
|
||||||
version: '1.0'
|
version: '1.1'
|
||||||
proxy:
|
proxy:
|
||||||
manual:
|
manual:
|
||||||
use_for_https:
|
use_for_https:
|
||||||
|
@ -216,7 +119,7 @@ Let's create the :file:`dict/05-proxy_manual_ssl_proxy.yml` file:
|
||||||
:linenos:
|
:linenos:
|
||||||
|
|
||||||
---
|
---
|
||||||
version: '1.0'
|
version: '1.1'
|
||||||
proxy:
|
proxy:
|
||||||
manual:
|
manual:
|
||||||
ssl_proxy:
|
ssl_proxy:
|
||||||
|
@ -271,9 +174,12 @@ Let's look at what happens if we try to access the `rougail.proxy.manual` variab
|
||||||
We have an error (with the message defined in the Jinja template):
|
We have an error (with the message defined in the Jinja template):
|
||||||
|
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: shell
|
||||||
|
|
||||||
|
tiramisu.error.PropertiesOptionError: cannot access to
|
||||||
|
optiondescription "Manual proxy configuration" because
|
||||||
|
has property "disabled" (the mode proxy is not manual)
|
||||||
|
|
||||||
tiramisu.error.PropertiesOptionError: cannot access to optiondescription "Manual proxy configuration" because has property "disabled" (the mode proxy is not manual)
|
|
||||||
|
|
||||||
Let's configure the proxy in manual mode
|
Let's configure the proxy in manual mode
|
||||||
|
|
||||||
|
@ -291,7 +197,7 @@ We can see that the returned variables does have the desired values:
|
||||||
'rougail.proxy.manual.http_proxy.port': '8080',
|
'rougail.proxy.manual.http_proxy.port': '8080',
|
||||||
'rougail.proxy.manual.use_for_https': True}
|
'rougail.proxy.manual.use_for_https': True}
|
||||||
|
|
||||||
Let's set the `read_only` mode:
|
Let's set the `read_only` mode and have a look at the configuration again:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
|
@ -307,7 +213,7 @@ Let's set the `read_only` mode:
|
||||||
In the `read_only` mode, we can see that the HTTPS configuration appears.
|
In the `read_only` mode, we can see that the HTTPS configuration appears.
|
||||||
|
|
||||||
.. note:: We can see that `rougail.proxy.manual.http_proxy` values have been copied
|
.. note:: We can see that `rougail.proxy.manual.http_proxy` values have been copied
|
||||||
in `rougail.proxy.manual.ssl_proxy` too...
|
in `rougail.proxy.manual.ssl_proxy` too.
|
||||||
|
|
||||||
Changing values programmatically
|
Changing values programmatically
|
||||||
--------------------------------------
|
--------------------------------------
|
||||||
|
@ -357,7 +263,7 @@ Let's create the :file:`dict/06-proxy_manual_socks_proxy.yml` file:
|
||||||
:caption: the :file:`dict/06-proxy_manual_socks_proxy.yml` file
|
:caption: the :file:`dict/06-proxy_manual_socks_proxy.yml` file
|
||||||
|
|
||||||
---
|
---
|
||||||
version: '1.0'
|
version: '1.1'
|
||||||
proxy:
|
proxy:
|
||||||
manual:
|
manual:
|
||||||
socks_proxy:
|
socks_proxy:
|
||||||
|
@ -389,7 +295,7 @@ Let's create the :file:`dict/07-proxy_auto.yml` file:
|
||||||
:caption: the :file:`dict/07-proxy_auto.yml` file
|
:caption: the :file:`dict/07-proxy_auto.yml` file
|
||||||
|
|
||||||
---
|
---
|
||||||
version: '1.0'
|
version: '1.1'
|
||||||
proxy:
|
proxy:
|
||||||
auto:
|
auto:
|
||||||
type: web_address
|
type: web_address
|
||||||
|
@ -416,7 +322,7 @@ Let's create the :file:`dict/07-proxy_no_proxy.yml` file:
|
||||||
:linenos:
|
:linenos:
|
||||||
|
|
||||||
---
|
---
|
||||||
version: '1.0'
|
version: '1.1'
|
||||||
proxy:
|
proxy:
|
||||||
no_proxy:
|
no_proxy:
|
||||||
description: Address for which proxy will be desactivated
|
description: Address for which proxy will be desactivated
|
||||||
|
@ -507,7 +413,7 @@ Nothing special when creating the authentication request. To do this, let's crea
|
||||||
:linenos:
|
:linenos:
|
||||||
|
|
||||||
---
|
---
|
||||||
version: '1.0'
|
version: '1.1'
|
||||||
proxy:
|
proxy:
|
||||||
prompt_authentication:
|
prompt_authentication:
|
||||||
description: Prompt for authentication if password is saved
|
description: Prompt for authentication if password is saved
|
||||||
|
@ -532,7 +438,7 @@ Let's create a `dict/09-proxy_proxy_dns_socks5.yml` file:
|
||||||
:linenos:
|
:linenos:
|
||||||
|
|
||||||
---
|
---
|
||||||
version: '1.0'
|
version: '1.1'
|
||||||
proxy:
|
proxy:
|
||||||
proxy_dns_socks5:
|
proxy_dns_socks5:
|
||||||
description: Use proxy DNS when using SOCKS v5
|
description: Use proxy DNS when using SOCKS v5
|
||||||
|
@ -575,7 +481,7 @@ Let's create a `dict/10-proxy_dns_over_https.yml` file:
|
||||||
:linenos:
|
:linenos:
|
||||||
|
|
||||||
---
|
---
|
||||||
version: '1.0'
|
version: '1.1'
|
||||||
proxy:
|
proxy:
|
||||||
dns_over_https:
|
dns_over_https:
|
||||||
description: DNS over HTTPS
|
description: DNS over HTTPS
|
||||||
|
@ -648,7 +554,7 @@ Here is the complete content of the FoxyProxy type proxy configuration
|
||||||
:linenos:
|
:linenos:
|
||||||
|
|
||||||
---
|
---
|
||||||
version: '1.0'
|
version: '1.1'
|
||||||
proxy:
|
proxy:
|
||||||
_type: leadership
|
_type: leadership
|
||||||
title:
|
title:
|
||||||
|
@ -810,7 +716,7 @@ If you prefer this option, here is a second extra dictionary :file:`foxyproxy/01
|
||||||
:linenos:
|
:linenos:
|
||||||
|
|
||||||
---
|
---
|
||||||
version: '1.0'
|
version: '1.1'
|
||||||
proxy:
|
proxy:
|
||||||
username:
|
username:
|
||||||
redefine: true
|
redefine: true
|
|
@ -1,27 +1,63 @@
|
||||||
The variables
|
The variables
|
||||||
===================
|
==============
|
||||||
|
|
||||||
Synopsis
|
Synopsis
|
||||||
------------
|
---------
|
||||||
|
|
||||||
.. glossary::
|
.. glossary::
|
||||||
|
|
||||||
variable
|
variable
|
||||||
variables
|
|
||||||
|
|
||||||
A variable is an abstract black box (container) paired with an associated symbolic name, which contains some defined or undefined quantity of data referred to as a `value`.
|
A variable is an abstract black box (container) paired with an associated symbolic name, wmost often an option configuration, hich contains some defined or undefined data setting referred to as a :term:`value`.
|
||||||
|
|
||||||
.. discussion:: This definition, makes a heavy use of data typing.
|
value
|
||||||
Indeed, depending on the type system definition of the constistency handling system used, variables may only be able to store a specified data type.
|
|
||||||
OK, variables are the containers for storing the values. It has something to do with typing.
|
|
||||||
But this is not just about typing.
|
|
||||||
|
|
||||||
Name
|
A value is a variable's setting.
|
||||||
-------------
|
Variable can have a default value, that is a setting defined in the :term:`structure file`,
|
||||||
|
or no value at all, then the value needs to be define later by the :term:`operator`.
|
||||||
|
|
||||||
Variable's associated symbolic name.
|
.. discussion:: Discussion
|
||||||
|
|
||||||
It's best to follow the :ref:`convention on variable names`.
|
The variable is, by definition, strongly typed.
|
||||||
|
Rougail uses static type definition and even type inference.
|
||||||
|
Indeed, the constistency handling system heavyly relies on the type system definition.
|
||||||
|
Variables may only be able to store a specified data type.
|
||||||
|
OK, variables are the containers for storing the values. It has something to do with typing.
|
||||||
|
|
||||||
|
But consitency handling system is is not just about strong typing. It is more than that.
|
||||||
|
|
||||||
|
Names
|
||||||
|
------
|
||||||
|
|
||||||
|
Variable name
|
||||||
|
|
||||||
|
Variable's associated symbolic name.
|
||||||
|
|
||||||
|
.. seealso::
|
||||||
|
|
||||||
|
Have a look at the :ref:`convention on variable naming link <convention on variable names>`.
|
||||||
|
|
||||||
|
Shorthand declaration
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
Shorthand declaration is a way to declare a variable in a single line. But you can only define variable name, description, multi or default value.
|
||||||
|
|
||||||
|
To create a variable, just add a key with it's name and default value as value.
|
||||||
|
Be careful not to declare any other attributes.
|
||||||
|
|
||||||
|
To declare a multi variable just add a list as default value.
|
||||||
|
|
||||||
|
By default, the description of the variable is the variable name.
|
||||||
|
If you add a comment in the same line of the name, this comment will be used has a description.
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
|
||||||
|
---
|
||||||
|
version: '1.1'
|
||||||
|
my_variable: 1 # This is a great integer variable
|
||||||
|
my_multi_variable: # This is a great multi string variable
|
||||||
|
- value1
|
||||||
|
- value2
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
-------------
|
-------------
|
||||||
|
@ -124,7 +160,7 @@ Parameters
|
||||||
* - **redefine**
|
* - **redefine**
|
||||||
|
|
||||||
`boolean`
|
`boolean`
|
||||||
- It is possible to define a variable in one :term:`dictionary` and change its behavior in a second :term:`dictionary`. In this case you must explicitly redefine the variable.
|
- It is possible to define a variable in one :term:`structure file` and change its behavior in a second :term:`structure file`. In this case you must explicitly redefine the variable.
|
||||||
|
|
||||||
**Default value**: `false`
|
**Default value**: `false`
|
||||||
* - **exists**
|
* - **exists**
|
||||||
|
@ -132,14 +168,14 @@ Parameters
|
||||||
`boolean`
|
`boolean`
|
||||||
- This attribute does two things:
|
- This attribute does two things:
|
||||||
|
|
||||||
- creates a variable if it does not exist in another :term:`dictionary` (otherwise do nothing), in this case the value of the attribute must be `true`
|
- creates a variable if it does not exist in another :term:`structure file` (otherwise do nothing), in this case the value of the attribute must be `true`
|
||||||
- in conjunction with the `redefine` attribute set to `true`, only modifies the behavior if it is pre-existing, in which case the attribute's value must be `false`.
|
- in conjunction with the `redefine` attribute set to `true`, only modifies the behavior if it is pre-existing, in which case the attribute's value must be `false`.
|
||||||
|
|
||||||
**Default value**: `null`
|
**Default value**: `null`
|
||||||
* - **test**
|
* - **test**
|
||||||
|
|
||||||
`list`
|
`list`
|
||||||
- The `test` attribute is a special attribute that allows :term:`dictionary` designers to influence a test robot by specifying useful values to test.
|
- The `test` attribute is a special attribute that allows :term:`structure file` designers to influence a test robot by specifying useful values to test.
|
||||||
|
|
||||||
Concretely, the content of this attribute is recorded in the `information` attribute of the corresponding `Tiramisu` option object.
|
Concretely, the content of this attribute is recorded in the `information` attribute of the corresponding `Tiramisu` option object.
|
||||||
|
|
||||||
|
|
|
@ -23,13 +23,17 @@ classifiers = [
|
||||||
|
|
||||||
]
|
]
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"pyyaml ~= 6.0.1",
|
"ruamel.yaml ~= 0.17.40",
|
||||||
"pydantic ~= 2.5.2",
|
"pydantic ~= 2.5.2",
|
||||||
"jinja2 ~= 3.1.2",
|
"jinja2 ~= 3.1.2",
|
||||||
|
"tiramisu ~= 4.1.0"
|
||||||
]
|
]
|
||||||
[project.optional-dependancies]
|
|
||||||
|
[project.optional-dependencies]
|
||||||
dev = [
|
dev = [
|
||||||
"pylint ~= 3.0.3",
|
"pylint ~= 3.0.3",
|
||||||
|
"pytest ~= 8.2.2",
|
||||||
|
"lxml ~= 5.2.2"
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.commitizen]
|
[tool.commitizen]
|
||||||
|
|
|
@ -28,19 +28,27 @@ along with this program; if not, write to the Free Software
|
||||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
"""
|
"""
|
||||||
from tiramisu import Config
|
from tiramisu import Config
|
||||||
from copy import copy
|
from tiramisu.error import PropertiesOptionError
|
||||||
|
from warnings import warn
|
||||||
|
from typing import List
|
||||||
|
|
||||||
from .convert import RougailConvert
|
from .convert import RougailConvert
|
||||||
from .config import RougailConfig
|
from .config import RougailConfig
|
||||||
from .update import RougailUpgrade
|
from .update import RougailUpgrade
|
||||||
from .object_model import CONVERT_OPTION
|
from .object_model import CONVERT_OPTION
|
||||||
|
from .utils import normalize_family
|
||||||
|
|
||||||
|
|
||||||
def tiramisu_display_name(kls) -> str:
|
def tiramisu_display_name(kls, subconfig) -> str:
|
||||||
"""Replace the Tiramisu display_name function to display path + description"""
|
"""Replace the Tiramisu display_name function to display path + description"""
|
||||||
doc = kls.impl_get_information("doc", None)
|
doc = kls._get_information(subconfig, "doc", None)
|
||||||
comment = f" ({doc})" if doc and doc != kls.impl_getname() else ""
|
comment = f" ({doc})" if doc and doc != kls.impl_getname() else ""
|
||||||
return f"{kls.impl_getpath()}{comment}"
|
if "{{ suffix }}" in comment:
|
||||||
|
comment = comment.replace('{{ suffix }}', str(subconfig.suffixes[-1]))
|
||||||
|
path = kls.impl_getpath()
|
||||||
|
if "{{ suffix }}" in path:
|
||||||
|
path = path.replace('{{ suffix }}', normalize_family(str(subconfig.suffixes[-1])))
|
||||||
|
return f"{path}{comment}"
|
||||||
|
|
||||||
|
|
||||||
class Rougail:
|
class Rougail:
|
||||||
|
@ -61,9 +69,10 @@ class Rougail:
|
||||||
path_prefix: str,
|
path_prefix: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Add a prefix"""
|
"""Add a prefix"""
|
||||||
|
self.converted.load_config()
|
||||||
self.converted.parse_directories(path_prefix)
|
self.converted.parse_directories(path_prefix)
|
||||||
|
|
||||||
def get_config(self):
|
def run(self):
|
||||||
"""Get Tiramisu Config"""
|
"""Get Tiramisu Config"""
|
||||||
if not self.config:
|
if not self.config:
|
||||||
tiram_obj = self.converted.save(self.rougailconfig["tiramisu_cache"])
|
tiram_obj = self.converted.save(self.rougailconfig["tiramisu_cache"])
|
||||||
|
@ -77,5 +86,47 @@ class Rougail:
|
||||||
self.config.property.read_write()
|
self.config.property.read_write()
|
||||||
return self.config
|
return self.config
|
||||||
|
|
||||||
|
def get_config(self):
|
||||||
|
warn("get_config is deprecated, use run instead", DeprecationWarning, stacklevel=2)
|
||||||
|
return self.run()
|
||||||
|
|
||||||
__ALL__ = ("Rougail", "RougailConfig", "RougailUpgrade")
|
def user_datas(self,
|
||||||
|
user_datas: List[dict]):
|
||||||
|
values = {}
|
||||||
|
errors = []
|
||||||
|
warnings = []
|
||||||
|
for datas in user_datas:
|
||||||
|
for name, data in datas.get('values', {}).items():
|
||||||
|
values.setdefault(name, {}).update(data)
|
||||||
|
errors.extend(datas.get('errors', []))
|
||||||
|
warnings.extend(datas.get('warnings', []))
|
||||||
|
while values:
|
||||||
|
value_is_set = False
|
||||||
|
for option in self.config:
|
||||||
|
if option.path() in values and option.index() in values[option.path()]:
|
||||||
|
try:
|
||||||
|
option.value.set(values[option.path()])
|
||||||
|
value_is_set = True
|
||||||
|
values.pop(option.path())
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
if not value_is_set:
|
||||||
|
break
|
||||||
|
for path, data in values.items():
|
||||||
|
for index, value in data.items():
|
||||||
|
try:
|
||||||
|
print('attention', path, value)
|
||||||
|
self.config.option(path).value.set(value)
|
||||||
|
print('pfff')
|
||||||
|
except AttributeError as err:
|
||||||
|
errors.append(str(err))
|
||||||
|
except ValueError as err:
|
||||||
|
errors.append(str(err).replace('"', "'"))
|
||||||
|
except PropertiesOptionError as err:
|
||||||
|
# warnings.append(f'"{err}" but is defined in "{self.filename}"')
|
||||||
|
warnings.append(str(err))
|
||||||
|
return {'errors': errors,
|
||||||
|
'warnings': warnings,
|
||||||
|
}
|
||||||
|
|
||||||
|
__all__ = ("Rougail", "RougailConfig", "RougailUpgrade")
|
||||||
|
|
|
@ -45,7 +45,7 @@ def get_annotators(annotators, module_name):
|
||||||
path = str(pathobj)
|
path = str(pathobj)
|
||||||
if not path.endswith(".py") or path.endswith("__.py"):
|
if not path.endswith(".py") or path.endswith("__.py"):
|
||||||
continue
|
continue
|
||||||
module = load_modules(path)
|
module = load_modules(module_name, path)
|
||||||
if "Annotator" not in dir(module):
|
if "Annotator" not in dir(module):
|
||||||
continue
|
continue
|
||||||
annotators[module_name].append(module.Annotator)
|
annotators[module_name].append(module.Annotator)
|
||||||
|
@ -62,21 +62,26 @@ class SpaceAnnotator: # pylint: disable=R0903
|
||||||
if ANNOTATORS is None:
|
if ANNOTATORS is None:
|
||||||
ANNOTATORS = {}
|
ANNOTATORS = {}
|
||||||
get_annotators(ANNOTATORS, "rougail.annotator")
|
get_annotators(ANNOTATORS, "rougail.annotator")
|
||||||
for extra_annotator in objectspace.rougailconfig["extra_annotators"]:
|
for extra_annotator in objectspace.extra_annotators:
|
||||||
if extra_annotator in ANNOTATORS:
|
if extra_annotator in ANNOTATORS:
|
||||||
continue
|
continue
|
||||||
get_annotators(ANNOTATORS, extra_annotator)
|
get_annotators(ANNOTATORS, extra_annotator)
|
||||||
|
for plugin in objectspace.plugins:
|
||||||
|
try:
|
||||||
|
get_annotators(ANNOTATORS, f'rougail.{plugin}.annotator')
|
||||||
|
except ModuleNotFoundError:
|
||||||
|
pass
|
||||||
annotators = ANNOTATORS["rougail.annotator"].copy()
|
annotators = ANNOTATORS["rougail.annotator"].copy()
|
||||||
for extra_annotator in objectspace.rougailconfig["extra_annotators"]:
|
for extra_annotator in objectspace.extra_annotators:
|
||||||
annotators.extend(ANNOTATORS[extra_annotator])
|
annotators.extend(ANNOTATORS[extra_annotator])
|
||||||
|
for plugin in objectspace.plugins:
|
||||||
|
annotators.extend(ANNOTATORS[f'rougail.{plugin}.annotator'])
|
||||||
annotators = sorted(annotators, key=get_level)
|
annotators = sorted(annotators, key=get_level)
|
||||||
functions = {}
|
functions = {}
|
||||||
functions_files = objectspace.rougailconfig["functions_file"]
|
functions_files = objectspace.functions_files
|
||||||
if not isinstance(functions_files, list):
|
|
||||||
functions_files = [functions_files]
|
|
||||||
for functions_file in functions_files:
|
for functions_file in functions_files:
|
||||||
if isfile(functions_file):
|
if isfile(functions_file):
|
||||||
loaded_modules = load_modules(functions_file)
|
loaded_modules = load_modules('function_file', functions_file)
|
||||||
for function in dir(loaded_modules):
|
for function in dir(loaded_modules):
|
||||||
if function.startswith("_"):
|
if function.startswith("_"):
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -31,6 +31,7 @@ from typing import Optional
|
||||||
from rougail.i18n import _
|
from rougail.i18n import _
|
||||||
from rougail.error import DictConsistencyError
|
from rougail.error import DictConsistencyError
|
||||||
from rougail.annotator.variable import Walk
|
from rougail.annotator.variable import Walk
|
||||||
|
from rougail.object_model import VariableCalculation
|
||||||
|
|
||||||
|
|
||||||
class Mode: # pylint: disable=R0903
|
class Mode: # pylint: disable=R0903
|
||||||
|
@ -63,16 +64,30 @@ class Annotator(Walk):
|
||||||
self.objectspace = objectspace
|
self.objectspace = objectspace
|
||||||
if not self.objectspace.paths:
|
if not self.objectspace.paths:
|
||||||
return
|
return
|
||||||
self.modes = {
|
self.check_leadership()
|
||||||
name: Mode(idx)
|
|
||||||
for idx, name in enumerate(self.objectspace.rougailconfig["modes_level"])
|
|
||||||
}
|
|
||||||
self.remove_empty_families()
|
self.remove_empty_families()
|
||||||
self.family_names()
|
self.family_names()
|
||||||
self.change_modes()
|
if self.objectspace.modes_level:
|
||||||
self.dynamic_families()
|
self.modes = {
|
||||||
|
name: Mode(idx)
|
||||||
|
for idx, name in enumerate(self.objectspace.modes_level)
|
||||||
|
}
|
||||||
|
self.default_variable_mode = self.objectspace.default_variable_mode
|
||||||
|
self.default_family_mode = self.objectspace.default_family_mode
|
||||||
|
self.change_modes()
|
||||||
self.convert_help()
|
self.convert_help()
|
||||||
|
|
||||||
|
def check_leadership(self) -> None:
|
||||||
|
"""No subfamily in a leadership"""
|
||||||
|
for family in self.get_families():
|
||||||
|
if family.type != "leadership":
|
||||||
|
continue
|
||||||
|
for variable_path in self.objectspace.parents[family.path]:
|
||||||
|
variable = self.objectspace.paths[variable_path]
|
||||||
|
if variable.type in self.objectspace.family_types:
|
||||||
|
msg = f'the leadership "{family.path}" cannot have the { variable.type } "{ variable.path}"'
|
||||||
|
raise DictConsistencyError(msg, 24, variable.xmlfiles)
|
||||||
|
|
||||||
def remove_empty_families(self) -> None:
|
def remove_empty_families(self) -> None:
|
||||||
"""Remove all families without any variable"""
|
"""Remove all families without any variable"""
|
||||||
removed_families = []
|
removed_families = []
|
||||||
|
@ -80,7 +95,7 @@ class Annotator(Walk):
|
||||||
if isinstance(family, self.objectspace.family) and not self._has_variable(
|
if isinstance(family, self.objectspace.family) and not self._has_variable(
|
||||||
family.path
|
family.path
|
||||||
):
|
):
|
||||||
if "." in family.path:
|
if self.objectspace.paths.default_namespace is None or "." in family.path:
|
||||||
removed_families.append(family.path)
|
removed_families.append(family.path)
|
||||||
removed_families.reverse()
|
removed_families.reverse()
|
||||||
for family in removed_families:
|
for family in removed_families:
|
||||||
|
@ -104,20 +119,17 @@ class Annotator(Walk):
|
||||||
if not family.description:
|
if not family.description:
|
||||||
family.description = family.name
|
family.description = family.name
|
||||||
|
|
||||||
# family.doc = family.description
|
|
||||||
# del family.description
|
|
||||||
|
|
||||||
def change_modes(self):
|
def change_modes(self):
|
||||||
"""change the mode of variables"""
|
"""change the mode of variables"""
|
||||||
modes_level = self.objectspace.rougailconfig["modes_level"]
|
modes_level = self.objectspace.modes_level
|
||||||
default_variable_mode = self.objectspace.rougailconfig["default_variable_mode"]
|
default_variable_mode = self.default_variable_mode
|
||||||
if default_variable_mode not in modes_level:
|
if default_variable_mode not in modes_level:
|
||||||
msg = _(
|
msg = _(
|
||||||
f'default variable mode "{default_variable_mode}" is not a valid mode, '
|
f'default variable mode "{default_variable_mode}" is not a valid mode, '
|
||||||
f"valid modes are {modes_level}"
|
f"valid modes are {modes_level}"
|
||||||
)
|
)
|
||||||
raise DictConsistencyError(msg, 72, None)
|
raise DictConsistencyError(msg, 72, None)
|
||||||
default_family_mode = self.objectspace.rougailconfig["default_family_mode"]
|
default_family_mode = self.default_family_mode
|
||||||
if default_family_mode not in modes_level:
|
if default_family_mode not in modes_level:
|
||||||
msg = _(
|
msg = _(
|
||||||
f'default family mode "{default_family_mode}" is not a valid mode, '
|
f'default family mode "{default_family_mode}" is not a valid mode, '
|
||||||
|
@ -131,12 +143,21 @@ class Annotator(Walk):
|
||||||
families.reverse()
|
families.reverse()
|
||||||
for family in families:
|
for family in families:
|
||||||
self._change_family_mode(family)
|
self._change_family_mode(family)
|
||||||
|
if self.objectspace.paths.default_namespace is None:
|
||||||
|
for variable_path in self.objectspace.parents['.']:
|
||||||
|
variable = self.objectspace.paths[variable_path]
|
||||||
|
if variable.type == "symlink" or variable_path in self.objectspace.families:
|
||||||
|
continue
|
||||||
|
self._set_default_mode_variable(variable,
|
||||||
|
self.default_variable_mode,
|
||||||
|
check_level=False,
|
||||||
|
)
|
||||||
|
|
||||||
def valid_mode(
|
def valid_mode(
|
||||||
self,
|
self,
|
||||||
obj,
|
obj,
|
||||||
) -> None:
|
) -> None:
|
||||||
modes_level = self.objectspace.rougailconfig["modes_level"]
|
modes_level = self.objectspace.modes_level
|
||||||
if self._has_mode(obj) and obj.mode not in modes_level:
|
if self._has_mode(obj) and obj.mode not in modes_level:
|
||||||
msg = _(
|
msg = _(
|
||||||
f'mode "{obj.mode}" for "{obj.name}" is not a valid mode, '
|
f'mode "{obj.mode}" for "{obj.name}" is not a valid mode, '
|
||||||
|
@ -162,6 +183,7 @@ class Annotator(Walk):
|
||||||
continue
|
continue
|
||||||
if leader is None and family.type == "leadership":
|
if leader is None and family.type == "leadership":
|
||||||
leader = variable
|
leader = variable
|
||||||
|
leader_mode = leader.mode
|
||||||
if variable_path in self.objectspace.families:
|
if variable_path in self.objectspace.families:
|
||||||
# set default mode a subfamily
|
# set default mode a subfamily
|
||||||
if family_mode and not self._has_mode(variable):
|
if family_mode and not self._has_mode(variable):
|
||||||
|
@ -172,9 +194,9 @@ class Annotator(Walk):
|
||||||
if leader:
|
if leader:
|
||||||
self._set_default_mode_leader(leader, variable)
|
self._set_default_mode_leader(leader, variable)
|
||||||
self._set_default_mode_variable(variable, family_mode)
|
self._set_default_mode_variable(variable, family_mode)
|
||||||
if leader:
|
if leader and leader_mode is not None:
|
||||||
# here because follower can change leader mode
|
# here because follower can change leader mode
|
||||||
self._set_auto_mode(family, leader.mode)
|
self._set_auto_mode(family, leader_mode)
|
||||||
|
|
||||||
def _has_mode(self, obj) -> bool:
|
def _has_mode(self, obj) -> bool:
|
||||||
return obj.mode and not obj.path in self.mode_auto
|
return obj.mode and not obj.path in self.mode_auto
|
||||||
|
@ -183,11 +205,12 @@ class Annotator(Walk):
|
||||||
self,
|
self,
|
||||||
variable: "self.objectspace.variable",
|
variable: "self.objectspace.variable",
|
||||||
family_mode: Optional[str],
|
family_mode: Optional[str],
|
||||||
|
check_level: bool=True,
|
||||||
) -> None:
|
) -> None:
|
||||||
# auto_save variable is set to 'basic' mode
|
# auto_save variable is set to 'basic' mode
|
||||||
# if its mode is not defined by the user
|
# if its mode is not defined by the user
|
||||||
if not self._has_mode(variable) and variable.auto_save is True:
|
if not self._has_mode(variable) and variable.auto_save is True:
|
||||||
variable.mode = self.objectspace.rougailconfig["modes_level"][0]
|
variable.mode = self.objectspace.modes_level[0]
|
||||||
# mandatory variable without value is a basic variable
|
# mandatory variable without value is a basic variable
|
||||||
elif (
|
elif (
|
||||||
not self._has_mode(variable)
|
not self._has_mode(variable)
|
||||||
|
@ -195,8 +218,8 @@ class Annotator(Walk):
|
||||||
and variable.default is None
|
and variable.default is None
|
||||||
and variable.path not in self.objectspace.default_multi
|
and variable.path not in self.objectspace.default_multi
|
||||||
):
|
):
|
||||||
variable_mode = self.objectspace.rougailconfig["modes_level"][0]
|
variable_mode = self.objectspace.modes_level[0]
|
||||||
if family_mode and self.modes[variable_mode] < self.modes[family_mode]:
|
if check_level and family_mode and self.modes[variable_mode] < self.modes[family_mode]:
|
||||||
msg = _(
|
msg = _(
|
||||||
f'the variable "{variable.name}" is mandatory so in "{variable_mode}" mode '
|
f'the variable "{variable.name}" is mandatory so in "{variable_mode}" mode '
|
||||||
f'but family has the higher family mode "{family_mode}"'
|
f'but family has the higher family mode "{family_mode}"'
|
||||||
|
@ -220,20 +243,17 @@ class Annotator(Walk):
|
||||||
leader: "self.objectspace.variable",
|
leader: "self.objectspace.variable",
|
||||||
follower: "self.objectspace.variable",
|
follower: "self.objectspace.variable",
|
||||||
) -> None:
|
) -> None:
|
||||||
if follower.auto_save is True:
|
|
||||||
msg = _(f'leader/followers "{follower.name}" could not be auto_save')
|
|
||||||
raise DictConsistencyError(msg, 29, follower.xmlfiles)
|
|
||||||
if leader == follower:
|
if leader == follower:
|
||||||
# it's a leader
|
# it's a leader
|
||||||
if not leader.mode:
|
if not leader.mode:
|
||||||
self._set_auto_mode(
|
self._set_auto_mode(
|
||||||
leader, self.objectspace.rougailconfig["default_variable_mode"]
|
leader, self.default_variable_mode
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
if self._has_mode(follower):
|
if self._has_mode(follower):
|
||||||
follower_mode = follower.mode
|
follower_mode = follower.mode
|
||||||
else:
|
else:
|
||||||
follower_mode = self.objectspace.rougailconfig["default_variable_mode"]
|
follower_mode = self.default_variable_mode
|
||||||
if self.modes[leader.mode] > self.modes[follower_mode]:
|
if self.modes[leader.mode] > self.modes[follower_mode]:
|
||||||
if self._has_mode(follower) and not self._has_mode(leader):
|
if self._has_mode(follower) and not self._has_mode(leader):
|
||||||
# if follower has mode but not the leader
|
# if follower has mode but not the leader
|
||||||
|
@ -255,20 +275,18 @@ class Annotator(Walk):
|
||||||
if family.mode:
|
if family.mode:
|
||||||
family_mode = family.mode
|
family_mode = family.mode
|
||||||
else:
|
else:
|
||||||
family_mode = self.objectspace.rougailconfig["default_family_mode"]
|
family_mode = self.default_family_mode
|
||||||
min_variable_mode = self.objectspace.rougailconfig["modes_level"][-1]
|
min_variable_mode = self.objectspace.modes_level[-1]
|
||||||
# change variable mode, but not if variables are not in a family
|
# change variable mode, but not if variables are not in a family
|
||||||
is_leadership = family.type == "leadership"
|
is_leadership = family.type == "leadership"
|
||||||
if family.path in self.objectspace.parents:
|
if family.path in self.objectspace.parents:
|
||||||
for idx, variable_path in enumerate(self.objectspace.parents[family.path]):
|
for variable_path in self.objectspace.parents[family.path]:
|
||||||
variable = self.objectspace.paths[variable_path]
|
variable = self.objectspace.paths[variable_path]
|
||||||
if variable.type == "symlink":
|
if variable.type == "symlink":
|
||||||
continue
|
continue
|
||||||
if variable_path in self.objectspace.families:
|
if variable_path in self.objectspace.families:
|
||||||
if not variable.mode:
|
if not variable.mode:
|
||||||
variable.mode = self.objectspace.rougailconfig[
|
variable.mode = self.default_family_mode
|
||||||
"default_family_mode"
|
|
||||||
]
|
|
||||||
else:
|
else:
|
||||||
self._change_variable_mode(variable, family_mode, is_leadership)
|
self._change_variable_mode(variable, family_mode, is_leadership)
|
||||||
if self.modes[min_variable_mode] > self.modes[variable.mode]:
|
if self.modes[min_variable_mode] > self.modes[variable.mode]:
|
||||||
|
@ -276,6 +294,10 @@ class Annotator(Walk):
|
||||||
if not family.mode:
|
if not family.mode:
|
||||||
# set the lower variable mode to family
|
# set the lower variable mode to family
|
||||||
self._set_auto_mode(family, min_variable_mode)
|
self._set_auto_mode(family, min_variable_mode)
|
||||||
|
if self.modes[family.mode] < self.modes[min_variable_mode]:
|
||||||
|
msg = _(f'the family "{family.name}" is in "{family.mode}" mode but variables and '
|
||||||
|
f'families inside have the higher modes "{min_variable_mode}"')
|
||||||
|
raise DictConsistencyError(msg, 62, family.xmlfiles)
|
||||||
|
|
||||||
def _change_variable_mode(
|
def _change_variable_mode(
|
||||||
self,
|
self,
|
||||||
|
@ -286,7 +308,7 @@ class Annotator(Walk):
|
||||||
if variable.mode:
|
if variable.mode:
|
||||||
variable_mode = variable.mode
|
variable_mode = variable.mode
|
||||||
else:
|
else:
|
||||||
variable_mode = self.objectspace.rougailconfig["default_variable_mode"]
|
variable_mode = self.default_variable_mode
|
||||||
# none basic variable in high level family has to be in high level
|
# none basic variable in high level family has to be in high level
|
||||||
if not is_follower and self.modes[variable_mode] < self.modes[family_mode]:
|
if not is_follower and self.modes[variable_mode] < self.modes[family_mode]:
|
||||||
if self._has_mode(variable):
|
if self._has_mode(variable):
|
||||||
|
@ -299,33 +321,6 @@ class Annotator(Walk):
|
||||||
if not variable.mode:
|
if not variable.mode:
|
||||||
variable.mode = variable_mode
|
variable.mode = variable_mode
|
||||||
|
|
||||||
def dynamic_families(self):
|
|
||||||
"""link dynamic families to object"""
|
|
||||||
for family in self.get_families():
|
|
||||||
if family.type != "dynamic":
|
|
||||||
continue
|
|
||||||
try:
|
|
||||||
family.variable = self.objectspace.paths[family.variable]
|
|
||||||
except AttributeError as err:
|
|
||||||
raise Exception(
|
|
||||||
f'cannot load the dynamic family "{family.path}", cannot find variable "{family.variable}"'
|
|
||||||
)
|
|
||||||
if not family.variable.multi:
|
|
||||||
msg = _(
|
|
||||||
f'dynamic family "{family.name}" must be linked '
|
|
||||||
f"to multi variable"
|
|
||||||
)
|
|
||||||
raise DictConsistencyError(msg, 16, family.xmlfiles)
|
|
||||||
for variable in self.objectspace.parents[family.path]:
|
|
||||||
if (
|
|
||||||
isinstance(variable, self.objectspace.family)
|
|
||||||
and not variable.leadership
|
|
||||||
):
|
|
||||||
msg = _(
|
|
||||||
f'dynamic family "{family.name}" cannot contains another family'
|
|
||||||
)
|
|
||||||
raise DictConsistencyError(msg, 22, family.xmlfiles)
|
|
||||||
|
|
||||||
def convert_help(self):
|
def convert_help(self):
|
||||||
"""Convert variable help"""
|
"""Convert variable help"""
|
||||||
for family in self.get_families():
|
for family in self.get_families():
|
||||||
|
|
|
@ -112,37 +112,37 @@ class Annotator(Walk):
|
||||||
self._convert_property(variable)
|
self._convert_property(variable)
|
||||||
if variable.hidden:
|
if variable.hidden:
|
||||||
if variable.hidden is True:
|
if variable.hidden is True:
|
||||||
self.frozen[variable.path] = True
|
self.frozen[path] = True
|
||||||
elif self.frozen.get(variable.path) is not True:
|
elif self.frozen.get(path) is not True:
|
||||||
self.frozen.setdefault(variable.path, []).append(variable.hidden)
|
self.frozen.setdefault(path, []).append(variable.hidden)
|
||||||
if variable.path in self.frozen:
|
if path in self.frozen:
|
||||||
frozen = self.frozen[variable.path]
|
frozen = self.frozen[path]
|
||||||
if frozen is True:
|
if frozen is True:
|
||||||
value = True
|
value = True
|
||||||
else:
|
else:
|
||||||
value = []
|
value = []
|
||||||
for calculation in frozen:
|
for calculation in frozen:
|
||||||
calculation_object = calculation.__class__
|
calculation_copy = calculation.copy()
|
||||||
calculation_dict = calculation.model_dump().copy()
|
calculation_copy.attribute_name = 'frozen'
|
||||||
calculation_dict["attribute_name"] = "frozen"
|
calculation_copy.ori_path = calculation_copy.path
|
||||||
calculation_dict["path"] = variable.path
|
calculation_copy.path = path
|
||||||
value.append(calculation_object(**calculation_dict))
|
value.append(calculation_copy)
|
||||||
if len(value) == 1:
|
if len(value) == 1:
|
||||||
value = value[0]
|
value = value[0]
|
||||||
self.objectspace.properties.add(path, "frozen", value)
|
self.objectspace.properties.add(path, "frozen", value)
|
||||||
if not variable.auto_save:
|
if not variable.auto_save:
|
||||||
# if auto_save, save calculated value
|
# if auto_save, save calculated value
|
||||||
self.objectspace.properties.add(path, "force_default_on_freeze", True)
|
self.objectspace.properties.add(path, "force_default_on_freeze", True)
|
||||||
if variable.mandatory and variable.multi:
|
if not variable.empty and self.objectspace.multis.get(variable.path, False):
|
||||||
# a multi could not have "None" has value
|
# a multi could not have "None" has value
|
||||||
# to permit it, just add mandatory="False"
|
# to permit it, just add empty="false"
|
||||||
self.objectspace.properties.add(path, "notempty", True)
|
self.objectspace.properties.add(path, "notempty", True)
|
||||||
if variable.unique:
|
if variable.unique:
|
||||||
self.objectspace.properties.add(path, "unique", True)
|
self.objectspace.properties.add(path, "unique", True)
|
||||||
if variable.unique is False:
|
if variable.unique is False:
|
||||||
self.objectspace.properties.add(path, "notunique", True)
|
self.objectspace.properties.add(path, "notunique", True)
|
||||||
if variable.auto_save:
|
if variable.auto_save:
|
||||||
self.objectspace.properties.add(variable.path, "force_store_value", True)
|
self.objectspace.properties.add(path, "force_store_value", True)
|
||||||
|
|
||||||
def _convert_property(
|
def _convert_property(
|
||||||
self,
|
self,
|
||||||
|
|
|
@ -48,13 +48,15 @@ class Annotator(Walk): # pylint: disable=R0903
|
||||||
return
|
return
|
||||||
self.objectspace = objectspace
|
self.objectspace = objectspace
|
||||||
self.convert_value()
|
self.convert_value()
|
||||||
self.add_choice_nil()
|
self.valid_choices()
|
||||||
|
|
||||||
def convert_value(self) -> None:
|
def convert_value(self) -> None:
|
||||||
"""convert value"""
|
"""convert value"""
|
||||||
for variable in self.get_variables():
|
for variable in self.get_variables():
|
||||||
if variable.type == "symlink":
|
if variable.type == "symlink":
|
||||||
continue
|
continue
|
||||||
|
if variable.version != '1.0' and variable.type == 'port':
|
||||||
|
self._convert_port(variable)
|
||||||
self._convert_value(variable)
|
self._convert_value(variable)
|
||||||
|
|
||||||
def _convert_value(
|
def _convert_value(
|
||||||
|
@ -66,58 +68,62 @@ class Annotator(Walk): # pylint: disable=R0903
|
||||||
if variable.type == "boolean" and multi is False and variable.default is None:
|
if variable.type == "boolean" and multi is False and variable.default is None:
|
||||||
variable.default = True
|
variable.default = True
|
||||||
|
|
||||||
if variable.default is None:
|
if variable.default is None or isinstance(variable.default, Calculation):
|
||||||
return
|
return
|
||||||
has_value = False
|
|
||||||
if isinstance(variable.default, Calculation):
|
|
||||||
pass
|
|
||||||
# variable.default = variable.default.to_function(self.functions)
|
|
||||||
elif isinstance(variable.default, list):
|
|
||||||
if not multi:
|
|
||||||
raise Exception(
|
|
||||||
f'The variable "{variable.path}" with a list has default value must have "multi" attribute'
|
|
||||||
)
|
|
||||||
if variable.path in self.objectspace.followers:
|
|
||||||
if multi != "submulti" and len(variable.default) != 1:
|
|
||||||
msg = _(
|
|
||||||
f'the follower "{variable.name}" without multi attribute can only have one value'
|
|
||||||
)
|
|
||||||
raise DictConsistencyError(msg, 87, variable.xmlfiles)
|
|
||||||
# else:
|
|
||||||
# variable.default = [value.name for value in variable.default]
|
|
||||||
if variable.path not in self.objectspace.leaders:
|
|
||||||
if multi == "submulti":
|
|
||||||
self.objectspace.default_multi[
|
|
||||||
variable.path
|
|
||||||
] = variable.default # [value.name for value in variable.value]
|
|
||||||
variable.default = None
|
|
||||||
else:
|
|
||||||
self.objectspace.default_multi[variable.path] = variable.default[
|
|
||||||
0
|
|
||||||
] # .name
|
|
||||||
has_value = True
|
|
||||||
elif variable.multi:
|
|
||||||
# msg = _(f'the none multi variable "{variable.name}" cannot have '
|
|
||||||
# 'more than one value')
|
|
||||||
# raise DictConsistencyError(msg, 68, variable.xmlfiles)
|
|
||||||
raise Exception("pfff")
|
|
||||||
else:
|
|
||||||
if variable.path in self.objectspace.followers:
|
|
||||||
self.objectspace.default_multi[variable.path] = variable.default
|
|
||||||
variable.default = None
|
|
||||||
has_value = True
|
|
||||||
|
|
||||||
def add_choice_nil(self) -> None:
|
if isinstance(variable.default, list):
|
||||||
|
if not multi:
|
||||||
|
msg = f'The variable "{variable.path}" with a list as default value must have "multi" attribute'
|
||||||
|
raise DictConsistencyError(msg, 68, variable.xmlfiles)
|
||||||
|
if variable.path in self.objectspace.followers and multi != "submulti":
|
||||||
|
msg = _(
|
||||||
|
f'the follower "{variable.name}" without multi attribute can only have one value'
|
||||||
|
)
|
||||||
|
raise DictConsistencyError(msg, 87, variable.xmlfiles)
|
||||||
|
if not variable.default:
|
||||||
|
variable.default = None
|
||||||
|
else:
|
||||||
|
if variable.path not in self.objectspace.leaders:
|
||||||
|
if multi == "submulti":
|
||||||
|
self.objectspace.default_multi[
|
||||||
|
variable.path
|
||||||
|
] = variable.default
|
||||||
|
variable.default = None
|
||||||
|
else:
|
||||||
|
self.objectspace.default_multi[variable.path] = variable.default[
|
||||||
|
0
|
||||||
|
]
|
||||||
|
elif variable.multi:
|
||||||
|
msg = _(f'the variable "{variable.name}" is multi but has a non list default value')
|
||||||
|
raise DictConsistencyError(msg, 12, variable.xmlfiles)
|
||||||
|
elif variable.path in self.objectspace.followers:
|
||||||
|
self.objectspace.default_multi[variable.path] = variable.default
|
||||||
|
variable.default = None
|
||||||
|
|
||||||
|
def _convert_port(self, variable) -> None:
|
||||||
|
if variable.multi is False and isinstance(variable.default, int):
|
||||||
|
variable.default = str(variable.default)
|
||||||
|
elif variable.multi is True and isinstance(variable.default, list):
|
||||||
|
for idx, value in enumerate(variable.default):
|
||||||
|
if isinstance(value, int):
|
||||||
|
variable.default[idx] = str(value)
|
||||||
|
|
||||||
|
def valid_choices(self) -> None:
|
||||||
"""A variable with type "Choice" that is not mandatory must has "nil" value"""
|
"""A variable with type "Choice" that is not mandatory must has "nil" value"""
|
||||||
for variable in self.get_variables():
|
for variable in self.get_variables():
|
||||||
if variable.type != "choice":
|
if variable.type != "choice":
|
||||||
continue
|
continue
|
||||||
is_none = False
|
|
||||||
if isinstance(variable.choices, Calculation):
|
if isinstance(variable.choices, Calculation):
|
||||||
continue
|
continue
|
||||||
for choice in variable.choices:
|
if variable.choices is None:
|
||||||
if choice is None:
|
msg = f'the variable "{variable.path}" is a "choice" variable but don\'t have any choice'
|
||||||
is_none = True
|
raise DictConsistencyError(msg, 19, variable.xmlfiles)
|
||||||
break
|
if not variable.mandatory and not variable.multi:
|
||||||
if not variable.mandatory and not is_none:
|
self.add_choice_nil(variable)
|
||||||
variable.choices.append(None)
|
|
||||||
|
def add_choice_nil(self, variable) -> None:
|
||||||
|
"""A variable with type "Choice" that is not mandatory must has "nil" value"""
|
||||||
|
for choice in variable.choices:
|
||||||
|
if choice is None:
|
||||||
|
return
|
||||||
|
variable.choices.append(None)
|
||||||
|
|
|
@ -30,7 +30,8 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
|
||||||
from rougail.i18n import _
|
from rougail.i18n import _
|
||||||
from rougail.error import DictConsistencyError
|
from rougail.error import DictConsistencyError
|
||||||
from rougail.object_model import Calculation
|
from rougail.object_model import Calculation, VariableCalculation
|
||||||
|
from tiramisu.error import display_list
|
||||||
|
|
||||||
|
|
||||||
class Walk:
|
class Walk:
|
||||||
|
@ -64,23 +65,92 @@ class Annotator(Walk): # pylint: disable=R0903
|
||||||
if not objectspace.paths:
|
if not objectspace.paths:
|
||||||
return
|
return
|
||||||
self.objectspace = objectspace
|
self.objectspace = objectspace
|
||||||
self.forbidden_name = [
|
if self.objectspace.main_namespace:
|
||||||
"services",
|
self.forbidden_name = [
|
||||||
self.objectspace.rougailconfig["variable_namespace"],
|
self.objectspace.main_namespace
|
||||||
]
|
]
|
||||||
for extra in self.objectspace.rougailconfig["extra_dictionaries"]:
|
for extra in self.objectspace.extra_dictionaries:
|
||||||
self.forbidden_name.append(extra)
|
self.forbidden_name.append(extra)
|
||||||
|
else:
|
||||||
|
self.forbidden_name = []
|
||||||
|
# default type inference from a default value with :term:`basic types`
|
||||||
|
self.basic_types = {str: "string", int: "number", bool: "boolean", float: "float"}
|
||||||
self.convert_variable()
|
self.convert_variable()
|
||||||
self.convert_test()
|
self.convert_test()
|
||||||
|
self.convert_examples()
|
||||||
self.convert_help()
|
self.convert_help()
|
||||||
|
self.verify_choices()
|
||||||
|
|
||||||
def convert_variable(self):
|
def convert_variable(self):
|
||||||
"""convert variable"""
|
"""convert variable"""
|
||||||
|
for variable in self.get_variables():
|
||||||
|
if variable.version != "1.0":
|
||||||
|
if variable.type == "symlink":
|
||||||
|
continue
|
||||||
|
self._convert_variable_inference(variable)
|
||||||
for variable in self.get_variables():
|
for variable in self.get_variables():
|
||||||
if variable.type == "symlink":
|
if variable.type == "symlink":
|
||||||
continue
|
continue
|
||||||
|
if variable.version != "1.0":
|
||||||
|
self._default_variable_copy_informations(variable)
|
||||||
|
if variable.multi is None:
|
||||||
|
variable.multi = False
|
||||||
|
if variable.type is None:
|
||||||
|
variable.type = "string"
|
||||||
|
self.objectspace.informations.add(
|
||||||
|
variable.path, "type", variable.type
|
||||||
|
)
|
||||||
self._convert_variable(variable)
|
self._convert_variable(variable)
|
||||||
|
|
||||||
|
def _convert_variable_inference(
|
||||||
|
self,
|
||||||
|
variable,
|
||||||
|
) -> None:
|
||||||
|
# variable has no type
|
||||||
|
if variable.type is None:
|
||||||
|
# choice type inference from the `choices` attribute
|
||||||
|
if variable.choices is not None:
|
||||||
|
variable.type = "choice"
|
||||||
|
elif variable.regexp is not None:
|
||||||
|
variable.type = "regexp"
|
||||||
|
elif variable.default not in [None, []]:
|
||||||
|
if isinstance(variable.default, list):
|
||||||
|
tested_value = variable.default[0]
|
||||||
|
else:
|
||||||
|
tested_value = variable.default
|
||||||
|
variable.type = self.basic_types.get(type(tested_value), None)
|
||||||
|
# variable has no multi attribute
|
||||||
|
if variable.multi is None and not (variable.type is None and isinstance(variable.default, VariableCalculation)):
|
||||||
|
if variable.path in self.objectspace.leaders:
|
||||||
|
variable.multi = True
|
||||||
|
else:
|
||||||
|
variable.multi = isinstance(variable.default, list)
|
||||||
|
|
||||||
|
def _default_variable_copy_informations(
|
||||||
|
self,
|
||||||
|
variable,
|
||||||
|
) -> None:
|
||||||
|
# if a variable has a variable as default value, that means the type/params or multi should has same value
|
||||||
|
if variable.type is not None or not isinstance(variable.default, VariableCalculation):
|
||||||
|
return
|
||||||
|
# copy type and params
|
||||||
|
calculated_variable_path = variable.default.variable
|
||||||
|
calculated_variable, suffix = self.objectspace.paths.get_with_dynamic(
|
||||||
|
calculated_variable_path, variable.default.path_prefix, variable.path, variable.version, variable.namespace, variable.xmlfiles
|
||||||
|
)
|
||||||
|
if calculated_variable is None:
|
||||||
|
return
|
||||||
|
variable.type = calculated_variable.type
|
||||||
|
if variable.params is None and calculated_variable.params is not None:
|
||||||
|
variable.params = calculated_variable.params
|
||||||
|
# copy multi attribut
|
||||||
|
if variable.multi is None:
|
||||||
|
calculated_path = calculated_variable.path
|
||||||
|
if calculated_path in self.objectspace.leaders and variable.path in self.objectspace.followers and calculated_path.rsplit('.')[0] == variable.path.rsplit('.')[0]:
|
||||||
|
variable.multi = False
|
||||||
|
else:
|
||||||
|
variable.multi = calculated_variable.multi
|
||||||
|
|
||||||
def _convert_variable(
|
def _convert_variable(
|
||||||
self,
|
self,
|
||||||
variable: dict,
|
variable: dict,
|
||||||
|
@ -97,25 +167,42 @@ class Annotator(Walk): # pylint: disable=R0903
|
||||||
self.objectspace.multis[variable.path] = True
|
self.objectspace.multis[variable.path] = True
|
||||||
if variable.path in self.objectspace.leaders:
|
if variable.path in self.objectspace.leaders:
|
||||||
if not self.objectspace.multis.get(variable.path, False):
|
if not self.objectspace.multis.get(variable.path, False):
|
||||||
msg = _(f'the variable "{variable.path}" in a leadership must be multi')
|
variable.multi = self.objectspace.multis[variable.path] = True
|
||||||
raise DictConsistencyError(msg, 32, variable.xmlfiles)
|
|
||||||
family = self.objectspace.paths[variable.path.rsplit(".", 1)[0]]
|
family = self.objectspace.paths[variable.path.rsplit(".", 1)[0]]
|
||||||
if variable.hidden:
|
if variable.hidden:
|
||||||
family.hidden = variable.hidden
|
family.hidden = variable.hidden
|
||||||
elif family.hidden:
|
elif family.hidden:
|
||||||
variable.hidden = family.hidden
|
variable.hidden = family.hidden
|
||||||
variable.hidden = None
|
variable.hidden = None
|
||||||
|
if variable.choices is not None and variable.type != 'choice':
|
||||||
|
msg = _(f'the variable "{variable.path}" has choices attribut but has not the "choice" type')
|
||||||
|
raise DictConsistencyError(msg, 11, variable.xmlfiles)
|
||||||
|
if variable.regexp is not None and variable.type != 'regexp':
|
||||||
|
msg = _(f'the variable "{variable.path}" has regexp attribut but has not the "regexp" type')
|
||||||
|
raise DictConsistencyError(msg, 37, variable.xmlfiles)
|
||||||
|
|
||||||
def convert_test(self):
|
def convert_test(self):
|
||||||
"""Convert variable tests value"""
|
"""Convert variable tests value"""
|
||||||
for variable in self.get_variables():
|
for variable in self.get_variables():
|
||||||
|
if variable.type == "symlink":
|
||||||
|
continue
|
||||||
if variable.test is None:
|
if variable.test is None:
|
||||||
# with we want remove test, we set "" has test value
|
|
||||||
continue
|
continue
|
||||||
self.objectspace.informations.add(
|
self.objectspace.informations.add(
|
||||||
variable.path, "test", tuple(variable.test)
|
variable.path, "test", tuple(variable.test)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def convert_examples(self):
|
||||||
|
"""Convert variable tests value"""
|
||||||
|
for variable in self.get_variables():
|
||||||
|
if variable.type == "symlink":
|
||||||
|
continue
|
||||||
|
if variable.examples is None:
|
||||||
|
continue
|
||||||
|
self.objectspace.informations.add(
|
||||||
|
variable.path, "examples", tuple(variable.examples)
|
||||||
|
)
|
||||||
|
|
||||||
def convert_help(self):
|
def convert_help(self):
|
||||||
"""Convert variable help"""
|
"""Convert variable help"""
|
||||||
for variable in self.get_variables():
|
for variable in self.get_variables():
|
||||||
|
@ -123,3 +210,28 @@ class Annotator(Walk): # pylint: disable=R0903
|
||||||
continue
|
continue
|
||||||
self.objectspace.informations.add(variable.path, "help", variable.help)
|
self.objectspace.informations.add(variable.path, "help", variable.help)
|
||||||
del variable.help
|
del variable.help
|
||||||
|
|
||||||
|
def verify_choices(self):
|
||||||
|
for variable in self.get_variables():
|
||||||
|
if variable.type != 'choice' or variable.default is None:
|
||||||
|
continue
|
||||||
|
if not isinstance(variable.choices, list):
|
||||||
|
continue
|
||||||
|
choices = variable.choices
|
||||||
|
has_calculation = False
|
||||||
|
for choice in choices:
|
||||||
|
if isinstance(choice, Calculation):
|
||||||
|
has_calculation = True
|
||||||
|
break
|
||||||
|
if has_calculation:
|
||||||
|
continue
|
||||||
|
|
||||||
|
default = variable.default
|
||||||
|
if not isinstance(default, list):
|
||||||
|
default = [default]
|
||||||
|
for value in default:
|
||||||
|
if isinstance(value, Calculation):
|
||||||
|
continue
|
||||||
|
if value not in choices:
|
||||||
|
msg = _(f'the variable "{variable.path}" has an unvalid default value "{value}" should be in {display_list(choices, separator="or", add_quote=True)}')
|
||||||
|
raise DictConsistencyError(msg, 26, variable.xmlfiles)
|
||||||
|
|
|
@ -28,54 +28,427 @@ You should have received a copy of the GNU General Public License
|
||||||
along with this program; if not, write to the Free Software
|
along with this program; if not, write to the Free Software
|
||||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
"""
|
"""
|
||||||
from os.path import join, abspath, dirname
|
from pathlib import Path
|
||||||
|
from tiramisu import Config
|
||||||
|
from ruamel.yaml import YAML
|
||||||
|
from .utils import _, load_modules, normalize_family
|
||||||
|
from .convert import RougailConvert
|
||||||
|
|
||||||
|
|
||||||
ROUGAILROOT = "/srv/rougail"
|
RENAMED = {'dictionaries_dir': 'main_dictionaries',
|
||||||
DTDDIR = join(dirname(abspath(__file__)), "data")
|
'variable_namespace': 'main_namespace',
|
||||||
|
'functions_file': 'functions_files',
|
||||||
|
}
|
||||||
|
NOT_IN_TIRAMISU = {'custom_types': {},
|
||||||
|
}
|
||||||
|
SUBMODULES = None
|
||||||
|
|
||||||
|
|
||||||
RougailConfig = {
|
def get_sub_modules():
|
||||||
"dictionaries_dir": [join(ROUGAILROOT, "dictionaries")],
|
global SUBMODULES
|
||||||
"extra_dictionaries": {},
|
if SUBMODULES is None:
|
||||||
"services_dir": [join(ROUGAILROOT, "services")],
|
SUBMODULES = {}
|
||||||
"patches_dir": join(ROUGAILROOT, "patches"),
|
for submodule in Path(__file__).parent.iterdir():
|
||||||
"templates_dir": join(ROUGAILROOT, "templates"),
|
if submodule.name.startswith('_') or not submodule.is_dir():
|
||||||
"destinations_dir": join(ROUGAILROOT, "destinations"),
|
continue
|
||||||
"tmp_dir": join(ROUGAILROOT, "tmp"),
|
config_file = submodule / 'config.py'
|
||||||
"dtdfilename": join(DTDDIR, "rougail.dtd"),
|
if config_file.is_file():
|
||||||
"yamlschema_filename": join(DTDDIR, "rougail.yml"),
|
SUBMODULES[submodule.name] = load_modules('rougail.' + submodule.name + '.config', str(config_file))
|
||||||
"functions_file": join(ROUGAILROOT, "functions.py"),
|
return SUBMODULES
|
||||||
"system_service_directory": "/usr/lib/systemd/system",
|
|
||||||
"systemd_service_destination_directory": "/usr/local/lib",
|
|
||||||
"systemd_service_directory": "/systemd",
|
def get_level(module):
|
||||||
"systemd_service_file": "rougail.conf",
|
return module['level']
|
||||||
"systemd_service_ip_file": "rougail_ip.conf",
|
|
||||||
"systemd_tmpfile_factory_dir": "/usr/local/lib",
|
|
||||||
"systemd_tmpfile_directory": "/tmpfiles.d",
|
class _RougailConfig:
|
||||||
"systemd_tmpfile_file": "0rougail.conf",
|
def __init__(self,
|
||||||
"systemd_tmpfile_delete_before_create": False,
|
backward_compatibility: bool,
|
||||||
"variable_namespace": "rougail",
|
root,
|
||||||
"variable_namespace_description": "Rougail",
|
extra_vars: dict
|
||||||
"auto_freeze_variable": "server_deployed",
|
):
|
||||||
"internal_functions": [],
|
self.backward_compatibility = backward_compatibility
|
||||||
"multi_functions": [],
|
self.root = root
|
||||||
"extra_annotators": [],
|
self.config = Config(
|
||||||
"modes_level": ["basic", "standard", "advanced"],
|
self.root,
|
||||||
"default_family_mode": "basic",
|
)
|
||||||
"default_variable_mode": "standard",
|
self.config.property.read_only()
|
||||||
"default_files_engine": "jinja",
|
self.extra_vars = extra_vars
|
||||||
"default_files_mode": 644,
|
self.not_in_tiramisu = NOT_IN_TIRAMISU | extra_vars
|
||||||
"default_files_owner": "root",
|
for variable, default_value in self.not_in_tiramisu.items():
|
||||||
"default_files_group": "root",
|
if not isinstance(default_value, str):
|
||||||
"default_files_included": "no",
|
default_value = default_value.copy()
|
||||||
"default_overrides_engine": "jinja",
|
setattr(self, variable, default_value)
|
||||||
"default_service_names_engine": "none",
|
|
||||||
"default_certificate_domain": "rougail.server_name",
|
def copy(self):
|
||||||
"base_option_name": "baseoption",
|
rougailconfig = _RougailConfig(self.backward_compatibility, self.root, self.extra_vars)
|
||||||
"export_with_import": True,
|
rougailconfig.config.value.importation(self.config.value.exportation())
|
||||||
"force_convert_dyn_option_description": False,
|
rougailconfig.config.property.importation(self.config.property.exportation())
|
||||||
"suffix": "",
|
rougailconfig.config.property.read_only()
|
||||||
"tiramisu_cache": None,
|
for variable in self.not_in_tiramisu:
|
||||||
"custom_types": {},
|
value = getattr(self, variable)
|
||||||
}
|
if not isinstance(value, str):
|
||||||
|
value = value.copy()
|
||||||
|
setattr(rougailconfig, variable, value)
|
||||||
|
return rougailconfig
|
||||||
|
|
||||||
|
def __setitem__(self,
|
||||||
|
key,
|
||||||
|
value,
|
||||||
|
) -> None:
|
||||||
|
if key in self.not_in_tiramisu:
|
||||||
|
setattr(self, key, value)
|
||||||
|
else:
|
||||||
|
self.config.property.read_write()
|
||||||
|
if key == 'export_with_import':
|
||||||
|
key = 'not_export_with_import'
|
||||||
|
key = RENAMED.get(key, key)
|
||||||
|
option = self.config.option(key)
|
||||||
|
if option.isoptiondescription() and option.isleadership():
|
||||||
|
leader = list(value)
|
||||||
|
option.leader().value.reset()
|
||||||
|
option.leader().value.set(leader)
|
||||||
|
follower = option.followers()[0]
|
||||||
|
for idx, val in enumerate(value.values()):
|
||||||
|
self.config.option(follower.path(), idx).value.set(val)
|
||||||
|
elif key == 'not_export_with_import':
|
||||||
|
option.value.set(not value)
|
||||||
|
else:
|
||||||
|
option.value.set(value)
|
||||||
|
self.config.property.read_only()
|
||||||
|
|
||||||
|
def __getitem__(self,
|
||||||
|
key,
|
||||||
|
) -> None:
|
||||||
|
if key in self.not_in_tiramisu:
|
||||||
|
return getattr(self, key)
|
||||||
|
if key == 'export_with_import':
|
||||||
|
key = 'not_export_with_import'
|
||||||
|
option = self.config.option(key)
|
||||||
|
if option.isoptiondescription() and option.isleadership():
|
||||||
|
return self.get_leadership(option)
|
||||||
|
ret = self.config.option(key).value.get()
|
||||||
|
if key == 'not_export_with_import':
|
||||||
|
return not ret
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def get_leadership(self,
|
||||||
|
option
|
||||||
|
) -> dict:
|
||||||
|
leader = None
|
||||||
|
followers = []
|
||||||
|
for opt, value in option.value.get().items():
|
||||||
|
if opt.issymlinkoption():
|
||||||
|
continue
|
||||||
|
if leader is None:
|
||||||
|
leader = value
|
||||||
|
else:
|
||||||
|
followers.append(value)
|
||||||
|
return dict(zip(leader, followers))
|
||||||
|
|
||||||
|
def parse(self, config) -> str:
|
||||||
|
for option in config:
|
||||||
|
if option.isoptiondescription():
|
||||||
|
yield from self.parse(option)
|
||||||
|
elif not option.issymlinkoption():
|
||||||
|
yield f'{option.path()}: {option.value.get()}'
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
self.config.property.read_write()
|
||||||
|
try:
|
||||||
|
values = "\n".join(self.parse(self.config))
|
||||||
|
except Exception as err:
|
||||||
|
values = str(err)
|
||||||
|
self.config.property.read_only()
|
||||||
|
return values
|
||||||
|
|
||||||
|
|
||||||
|
class FakeRougailConvert(RougailConvert):
|
||||||
|
def __init__(self,
|
||||||
|
add_extra_options: bool,
|
||||||
|
) -> None:
|
||||||
|
self.add_extra_options = add_extra_options
|
||||||
|
super().__init__({})
|
||||||
|
|
||||||
|
def load_config(self) -> None:
|
||||||
|
self.sort_dictionaries_all = False
|
||||||
|
self.main_namespace = None
|
||||||
|
self.suffix = ''
|
||||||
|
self.custom_types = {}
|
||||||
|
self.functions_files = []
|
||||||
|
self.modes_level = []
|
||||||
|
self.extra_annotators = []
|
||||||
|
self.base_option_name = "baseoption"
|
||||||
|
self.export_with_import = True
|
||||||
|
self.internal_functions = []
|
||||||
|
self.plugins = ['structural_commandline']
|
||||||
|
self.add_extra_options = self.add_extra_options
|
||||||
|
|
||||||
|
|
||||||
|
def get_rougail_config(*,
|
||||||
|
backward_compatibility: bool=True,
|
||||||
|
add_extra_options: bool=True,
|
||||||
|
) -> _RougailConfig:
|
||||||
|
if backward_compatibility:
|
||||||
|
main_namespace_default = 'rougail'
|
||||||
|
else:
|
||||||
|
main_namespace_default = 'null'
|
||||||
|
rougail_options = """default_dictionary_format_version:
|
||||||
|
description: Dictionary format version by default, if not specified in dictionary file
|
||||||
|
alternative_name: v
|
||||||
|
choices:
|
||||||
|
- '1.0'
|
||||||
|
- '1.1'
|
||||||
|
mandatory: false
|
||||||
|
|
||||||
|
main_dictionaries:
|
||||||
|
description: 'Directories where dictionary files are placed'
|
||||||
|
type: unix_filename
|
||||||
|
alternative_name: m
|
||||||
|
params:
|
||||||
|
allow_relative: True
|
||||||
|
test_existence: True
|
||||||
|
types:
|
||||||
|
- directory
|
||||||
|
multi: true
|
||||||
|
|
||||||
|
sort_dictionaries_all:
|
||||||
|
description: Sort dictionaries from differents directories
|
||||||
|
negative_description: Sort dictionaries directory by directory
|
||||||
|
default: false
|
||||||
|
|
||||||
|
main_namespace:
|
||||||
|
description: Main namespace name
|
||||||
|
default: MAIN_MAMESPACE_DEFAULT
|
||||||
|
alternative_name: s
|
||||||
|
mandatory: false
|
||||||
|
|
||||||
|
extra_dictionaries:
|
||||||
|
description: Extra namespaces
|
||||||
|
type: leadership
|
||||||
|
disabled:
|
||||||
|
variable: main_namespace
|
||||||
|
when: null
|
||||||
|
|
||||||
|
names:
|
||||||
|
description: 'Extra namespace name'
|
||||||
|
alternative_name: xn
|
||||||
|
multi: true
|
||||||
|
mandatory: false
|
||||||
|
|
||||||
|
directories:
|
||||||
|
description: Directories where extra dictionary files are placed
|
||||||
|
alternative_name: xd
|
||||||
|
type: unix_filename
|
||||||
|
params:
|
||||||
|
allow_relative: true
|
||||||
|
test_existence: true
|
||||||
|
types:
|
||||||
|
- directory
|
||||||
|
multi: true
|
||||||
|
|
||||||
|
upgrade:
|
||||||
|
description: Update dictionaries to newest Rougail format version
|
||||||
|
negative_description: Do not update dictionaries to newest Rougail format version
|
||||||
|
default: false
|
||||||
|
|
||||||
|
upgrade_options:
|
||||||
|
description: Update informations
|
||||||
|
disabled:
|
||||||
|
variable: upgrade
|
||||||
|
when: false
|
||||||
|
|
||||||
|
main_dictionaries:
|
||||||
|
description: 'Directories where dictionary files will be placed'
|
||||||
|
default:
|
||||||
|
variable: __.main_dictionaries
|
||||||
|
|
||||||
|
extra_dictionary:
|
||||||
|
description: 'Directories where extra files will be placed'
|
||||||
|
type: unix_filename
|
||||||
|
params:
|
||||||
|
allow_relative: true
|
||||||
|
test_existence: true
|
||||||
|
types:
|
||||||
|
- directory
|
||||||
|
disabled:
|
||||||
|
variable: __.main_namespace
|
||||||
|
when: null
|
||||||
|
|
||||||
|
functions_files:
|
||||||
|
description: File with functions
|
||||||
|
alternative_name: c
|
||||||
|
type: unix_filename
|
||||||
|
params:
|
||||||
|
allow_relative: true
|
||||||
|
test_existence: true
|
||||||
|
types:
|
||||||
|
- file
|
||||||
|
multi: true
|
||||||
|
mandatory: false
|
||||||
|
|
||||||
|
modes_level:
|
||||||
|
description: All modes level available
|
||||||
|
default:
|
||||||
|
- basic
|
||||||
|
- standard
|
||||||
|
- advanced
|
||||||
|
commandline: false
|
||||||
|
|
||||||
|
default_family_mode:
|
||||||
|
description: Default mode for a family
|
||||||
|
default:
|
||||||
|
type: jinja
|
||||||
|
jinja: |
|
||||||
|
{{ modes_level[0] }}
|
||||||
|
validators:
|
||||||
|
- type: jinja
|
||||||
|
jinja: |
|
||||||
|
{% if default_family_mode not in modes_level %}
|
||||||
|
not in modes_level ({modes_level})
|
||||||
|
{% endif %}
|
||||||
|
commandline: false
|
||||||
|
|
||||||
|
default_variable_mode:
|
||||||
|
description: Default mode for a variable
|
||||||
|
default:
|
||||||
|
type: jinja
|
||||||
|
jinja: |
|
||||||
|
{{ modes_level[1] }}
|
||||||
|
validators:
|
||||||
|
- type: jinja
|
||||||
|
jinja: |
|
||||||
|
{% if default_variable_mode not in modes_level %}
|
||||||
|
not in modes_level ({modes_level})
|
||||||
|
{% endif %}
|
||||||
|
commandline: false
|
||||||
|
|
||||||
|
base_option_name:
|
||||||
|
description: Option name for the base option
|
||||||
|
default: baseoption
|
||||||
|
commandline: false
|
||||||
|
|
||||||
|
not_export_with_import:
|
||||||
|
description: In cache file, do not importation of Tiramisu and other dependencies
|
||||||
|
default: false
|
||||||
|
commandline: false
|
||||||
|
|
||||||
|
tiramisu_cache:
|
||||||
|
description: Tiramisu cache filename
|
||||||
|
alternative_name: t
|
||||||
|
type: unix_filename
|
||||||
|
mandatory: false
|
||||||
|
params:
|
||||||
|
allow_relative: true
|
||||||
|
|
||||||
|
internal_functions:
|
||||||
|
description: Name of internal functions that we can use as a function
|
||||||
|
multi: true
|
||||||
|
mandatory: false
|
||||||
|
commandline: false
|
||||||
|
|
||||||
|
extra_annotators:
|
||||||
|
description: Name of extra annotators
|
||||||
|
multi: true
|
||||||
|
mandatory: false
|
||||||
|
commandline: false
|
||||||
|
|
||||||
|
plugins:
|
||||||
|
description: Name of Rougail plugins
|
||||||
|
multi: true
|
||||||
|
mandatory: false
|
||||||
|
commandline: false
|
||||||
|
|
||||||
|
suffix:
|
||||||
|
description: Suffix add to generated option name
|
||||||
|
default: ''
|
||||||
|
mandatory: false
|
||||||
|
commandline: false
|
||||||
|
""".replace('MAIN_MAMESPACE_DEFAULT', main_namespace_default)
|
||||||
|
processes = {'structural': [],
|
||||||
|
'output': [],
|
||||||
|
'user data': [],
|
||||||
|
}
|
||||||
|
for module in get_sub_modules().values():
|
||||||
|
data = module.get_rougail_config()
|
||||||
|
processes[data['process']].append(data)
|
||||||
|
# reorder
|
||||||
|
for process in processes:
|
||||||
|
processes[process] = list(sorted(processes[process], key=get_level))
|
||||||
|
rougail_process = """step: # Load and exporter steps
|
||||||
|
disabled:
|
||||||
|
variable: upgrade"""
|
||||||
|
for process in processes:
|
||||||
|
if processes[process]:
|
||||||
|
objects = processes[process]
|
||||||
|
rougail_process += """
|
||||||
|
{NAME}:
|
||||||
|
description: Select for {NAME}
|
||||||
|
alternative_name: {NAME[0]}
|
||||||
|
choices:
|
||||||
|
""".format(NAME=normalize_family(process),
|
||||||
|
)
|
||||||
|
for obj in objects:
|
||||||
|
rougail_process += f" - {obj['name']}\n"
|
||||||
|
if process == 'structural':
|
||||||
|
rougail_process += " commandline: false"
|
||||||
|
elif process == 'user data':
|
||||||
|
rougail_process += """ multi: true
|
||||||
|
mandatory: false
|
||||||
|
"""
|
||||||
|
hidden_outputs = [process['name'] for process in processes['output'] if not process.get('allow_user_data', True)]
|
||||||
|
if hidden_outputs:
|
||||||
|
rougail_process += """ hidden:
|
||||||
|
type: jinja
|
||||||
|
jinja: |
|
||||||
|
"""
|
||||||
|
for hidden_output in hidden_outputs:
|
||||||
|
rougail_process += """ {% if _.output == 'NAME' %}
|
||||||
|
Cannot load user data for NAME output
|
||||||
|
{% endif %}
|
||||||
|
""".replace('NAME', hidden_output)
|
||||||
|
else:
|
||||||
|
rougail_process += ' default: {DEFAULT}'.format(DEFAULT=objects[0]['name'])
|
||||||
|
else:
|
||||||
|
rougail_process += """
|
||||||
|
{NAME}:
|
||||||
|
description: Select for {NAME}
|
||||||
|
hidden: true
|
||||||
|
mandatory: false
|
||||||
|
multi: true
|
||||||
|
""".format(NAME=normalize_family(process),
|
||||||
|
)
|
||||||
|
rougail_options += rougail_process
|
||||||
|
convert = FakeRougailConvert(add_extra_options)
|
||||||
|
convert._init()
|
||||||
|
convert.namespace = None
|
||||||
|
convert.parse_root_file(
|
||||||
|
'rougail.config',
|
||||||
|
'',
|
||||||
|
'1.1',
|
||||||
|
YAML().load(rougail_options),
|
||||||
|
)
|
||||||
|
extra_vars = {}
|
||||||
|
for process in processes:
|
||||||
|
for obj in processes[process]:
|
||||||
|
if 'extra_vars' in obj:
|
||||||
|
extra_vars |= obj['extra_vars']
|
||||||
|
if not 'options' in obj:
|
||||||
|
continue
|
||||||
|
convert.parse_root_file(
|
||||||
|
f'rougail.config.{obj["name"]}',
|
||||||
|
'',
|
||||||
|
'1.1',
|
||||||
|
YAML().load(obj['options']),
|
||||||
|
)
|
||||||
|
|
||||||
|
tiram_obj = convert.save(None)
|
||||||
|
optiondescription = {}
|
||||||
|
exec(tiram_obj, {}, optiondescription) # pylint: disable=W0122
|
||||||
|
return _RougailConfig(backward_compatibility,
|
||||||
|
optiondescription["option_0"],
|
||||||
|
extra_vars=extra_vars,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
RougailConfig = get_rougail_config()
|
||||||
|
|
|
@ -73,3 +73,14 @@ class DictConsistencyError(Exception):
|
||||||
|
|
||||||
class UpgradeError(Exception):
|
class UpgradeError(Exception):
|
||||||
"""Error during XML upgrade"""
|
"""Error during XML upgrade"""
|
||||||
|
|
||||||
|
## ---- generic exceptions ----
|
||||||
|
|
||||||
|
class NotFoundError(Exception):
|
||||||
|
"not found error"
|
||||||
|
pass
|
||||||
|
|
||||||
|
## ---- specific exceptions ----
|
||||||
|
|
||||||
|
class VariableCalculationDependencyError(Exception):
|
||||||
|
pass
|
||||||
|
|
|
@ -29,10 +29,12 @@ from pydantic import (
|
||||||
StrictStr,
|
StrictStr,
|
||||||
ConfigDict,
|
ConfigDict,
|
||||||
)
|
)
|
||||||
|
from tiramisu import undefined
|
||||||
from .utils import get_jinja_variable_to_param, get_realpath
|
from .utils import get_jinja_variable_to_param, get_realpath
|
||||||
|
from .error import DictConsistencyError, VariableCalculationDependencyError
|
||||||
|
|
||||||
BASETYPE = Union[StrictBool, StrictInt, StrictFloat, StrictStr, None]
|
BASETYPE = Union[StrictBool, StrictInt, StrictFloat, StrictStr, None]
|
||||||
|
PROPERTY_ATTRIBUTE = ["frozen", "hidden", "disabled", "mandatory"]
|
||||||
|
|
||||||
|
|
||||||
def convert_boolean(value: str) -> bool:
|
def convert_boolean(value: str) -> bool:
|
||||||
|
@ -44,44 +46,63 @@ def convert_boolean(value: str) -> bool:
|
||||||
return True
|
return True
|
||||||
elif value == "false":
|
elif value == "false":
|
||||||
return False
|
return False
|
||||||
raise Exception(f"unknown boolean value {value}")
|
elif value in ["", None]:
|
||||||
|
return None
|
||||||
|
raise Exception(f'unknown boolean value "{value}"')
|
||||||
|
|
||||||
|
|
||||||
CONVERT_OPTION = {
|
CONVERT_OPTION = {
|
||||||
"string": dict(opttype="StrOption"),
|
"string": dict(opttype="StrOption", example="example"),
|
||||||
"number": dict(opttype="IntOption", func=int),
|
"number": dict(opttype="IntOption", func=int, example=42),
|
||||||
"float": dict(opttype="FloatOption", func=float),
|
"float": dict(opttype="FloatOption", func=float, example=1.42),
|
||||||
"boolean": dict(opttype="BoolOption", func=convert_boolean),
|
"boolean": dict(opttype="BoolOption", func=convert_boolean),
|
||||||
"secret": dict(opttype="PasswordOption"),
|
"secret": dict(opttype="PasswordOption", example="secrets"),
|
||||||
"mail": dict(opttype="EmailOption"),
|
"mail": dict(opttype="EmailOption", example="user@example.net"),
|
||||||
"unix_filename": dict(opttype="FilenameOption"),
|
"unix_filename": dict(opttype="FilenameOption", example="/tmp/myfile.txt"),
|
||||||
"date": dict(opttype="DateOption"),
|
"date": dict(opttype="DateOption", example="2000-01-01"),
|
||||||
"unix_user": dict(opttype="UsernameOption"),
|
"unix_user": dict(opttype="UsernameOption", example="username"),
|
||||||
"ip": dict(opttype="IPOption", initkwargs={"allow_reserved": True}),
|
"ip": dict(
|
||||||
"cidr": dict(opttype="IPOption", initkwargs={"cidr": True}),
|
opttype="IPOption", initkwargs={"allow_reserved": True}, example="1.1.1.1"
|
||||||
"netmask": dict(opttype="NetmaskOption"),
|
),
|
||||||
"network": dict(opttype="NetworkOption"),
|
"cidr": dict(opttype="IPOption", initkwargs={"cidr": True}, example="1.1.1.0/24"),
|
||||||
"network_cidr": dict(opttype="NetworkOption", initkwargs={"cidr": True}),
|
"netmask": dict(opttype="NetmaskOption", example="255.255.255.0"),
|
||||||
"broadcast": dict(opttype="BroadcastOption"),
|
"network": dict(opttype="NetworkOption", example="1.1.1.0"),
|
||||||
|
"network_cidr": dict(
|
||||||
|
opttype="NetworkOption", initkwargs={"cidr": True}, example="1.1.1.0/24"
|
||||||
|
),
|
||||||
|
"broadcast": dict(opttype="BroadcastOption", example="1.1.1.255"),
|
||||||
"netbios": dict(
|
"netbios": dict(
|
||||||
opttype="DomainnameOption",
|
opttype="DomainnameOption",
|
||||||
initkwargs={"type": "netbios", "warnings_only": True},
|
initkwargs={"type": "netbios", "warnings_only": True},
|
||||||
|
example="example",
|
||||||
),
|
),
|
||||||
"domainname": dict(
|
"domainname": dict(
|
||||||
opttype="DomainnameOption", initkwargs={"type": "domainname", "allow_ip": False}
|
opttype="DomainnameOption",
|
||||||
|
initkwargs={"type": "domainname", "allow_ip": False},
|
||||||
|
example="example.net",
|
||||||
),
|
),
|
||||||
"hostname": dict(
|
"hostname": dict(
|
||||||
opttype="DomainnameOption", initkwargs={"type": "hostname", "allow_ip": False}
|
opttype="DomainnameOption",
|
||||||
|
initkwargs={"type": "hostname", "allow_ip": False},
|
||||||
|
example="example",
|
||||||
),
|
),
|
||||||
"web_address": dict(
|
"web_address": dict(
|
||||||
opttype="URLOption", initkwargs={"allow_ip": False, "allow_without_dot": True}
|
opttype="URLOption",
|
||||||
|
initkwargs={"allow_ip": False, "allow_without_dot": True},
|
||||||
|
example="https://example.net",
|
||||||
),
|
),
|
||||||
"port": dict(opttype="PortOption", initkwargs={"allow_private": True}),
|
"port": dict(
|
||||||
"mac": dict(opttype="MACOption"),
|
opttype="PortOption", initkwargs={"allow_private": True}, example="111"
|
||||||
|
),
|
||||||
|
"mac": dict(opttype="MACOption", example="00:00:00:00:00"),
|
||||||
"unix_permissions": dict(
|
"unix_permissions": dict(
|
||||||
opttype="PermissionsOption", initkwargs={"warnings_only": True}, func=int
|
opttype="PermissionsOption",
|
||||||
|
initkwargs={"warnings_only": True},
|
||||||
|
func=int,
|
||||||
|
example="644",
|
||||||
),
|
),
|
||||||
"choice": dict(opttype="ChoiceOption"),
|
"choice": dict(opttype="ChoiceOption", example="a_choice"),
|
||||||
|
"regexp": dict(opttype="RegexpOption"),
|
||||||
#
|
#
|
||||||
"symlink": dict(opttype="SymLinkOption"),
|
"symlink": dict(opttype="SymLinkOption"),
|
||||||
}
|
}
|
||||||
|
@ -89,24 +110,45 @@ CONVERT_OPTION = {
|
||||||
|
|
||||||
class Param(BaseModel):
|
class Param(BaseModel):
|
||||||
key: str
|
key: str
|
||||||
|
|
||||||
model_config = ConfigDict(extra="forbid")
|
model_config = ConfigDict(extra="forbid")
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
path,
|
||||||
|
attribute,
|
||||||
|
family_is_dynamic,
|
||||||
|
is_follower,
|
||||||
|
xmlfiles,
|
||||||
|
**kwargs,
|
||||||
|
) -> None:
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
class AnyParam(Param):
|
class AnyParam(Param):
|
||||||
type: str
|
type: str
|
||||||
value: BASETYPE
|
value: Union[BASETYPE, List[BASETYPE]]
|
||||||
|
|
||||||
|
|
||||||
class VariableParam(Param):
|
class VariableParam(Param):
|
||||||
type: str
|
type: str
|
||||||
variable: str
|
variable: str
|
||||||
propertyerror: bool = True
|
propertyerror: bool = True
|
||||||
|
whole: bool = False
|
||||||
optional: bool = False
|
optional: bool = False
|
||||||
|
|
||||||
|
|
||||||
class SuffixParam(Param):
|
class SuffixParam(Param):
|
||||||
type: str
|
type: str
|
||||||
|
suffix: Optional[int] = None
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
**kwargs,
|
||||||
|
) -> None:
|
||||||
|
if not kwargs["family_is_dynamic"]:
|
||||||
|
msg = f'suffix parameter for "{kwargs["attribute"]}" in "{kwargs["path"]}" cannot be set none dynamic family'
|
||||||
|
raise DictConsistencyError(msg, 10, kwargs["xmlfiles"])
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
class InformationParam(Param):
|
class InformationParam(Param):
|
||||||
|
@ -118,6 +160,16 @@ class InformationParam(Param):
|
||||||
class IndexParam(Param):
|
class IndexParam(Param):
|
||||||
type: str
|
type: str
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
**kwargs,
|
||||||
|
) -> None:
|
||||||
|
|
||||||
|
if not kwargs["is_follower"]:
|
||||||
|
msg = f'the variable "{kwargs["path"]}" is not a follower, so cannot have index type for param in "{kwargs["attribute"]}"'
|
||||||
|
raise DictConsistencyError(msg, 25, kwargs["xmlfiles"])
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
PARAM_TYPES = {
|
PARAM_TYPES = {
|
||||||
"any": AnyParam,
|
"any": AnyParam,
|
||||||
|
@ -132,6 +184,11 @@ class Calculation(BaseModel):
|
||||||
path_prefix: Optional[str]
|
path_prefix: Optional[str]
|
||||||
path: str
|
path: str
|
||||||
inside_list: bool
|
inside_list: bool
|
||||||
|
version: str
|
||||||
|
ori_path: Optional[str] = None
|
||||||
|
default_values: Any = None
|
||||||
|
namespace: Optional[str]
|
||||||
|
xmlfiles: List[str]
|
||||||
|
|
||||||
model_config = ConfigDict(extra="forbid")
|
model_config = ConfigDict(extra="forbid")
|
||||||
|
|
||||||
|
@ -148,24 +205,49 @@ class Calculation(BaseModel):
|
||||||
for param_obj in self.params:
|
for param_obj in self.params:
|
||||||
param = param_obj.model_dump()
|
param = param_obj.model_dump()
|
||||||
if param.get("type") == "variable":
|
if param.get("type") == "variable":
|
||||||
variable_path = self.get_realpath(param["variable"])
|
if self.ori_path is None:
|
||||||
variable, suffix, dynamic = objectspace.paths.get_with_dynamic(variable_path)
|
path = self.path
|
||||||
|
else:
|
||||||
|
path = self.ori_path
|
||||||
|
variable, suffix = objectspace.paths.get_with_dynamic(
|
||||||
|
param["variable"],
|
||||||
|
self.path_prefix,
|
||||||
|
path,
|
||||||
|
self.version,
|
||||||
|
self.namespace,
|
||||||
|
self.xmlfiles,
|
||||||
|
)
|
||||||
if not variable:
|
if not variable:
|
||||||
if not param.get("optional"):
|
if not param.get("optional"):
|
||||||
raise Exception(f"cannot find {variable_path}")
|
msg = f'cannot find variable "{param["variable"]}" defined attribute in "{self.attribute_name}" for "{self.path}"'
|
||||||
|
raise DictConsistencyError(msg, 22, self.xmlfiles)
|
||||||
continue
|
continue
|
||||||
if not isinstance(variable, objectspace.variable):
|
if not isinstance(variable, objectspace.variable):
|
||||||
raise Exception("pfff it's a family")
|
raise Exception("pfff it's a family")
|
||||||
param["variable"] = variable
|
param["variable"] = variable
|
||||||
if suffix:
|
if suffix:
|
||||||
param["suffix"] = suffix
|
param["suffix"] = suffix
|
||||||
param["dynamic"] = dynamic
|
|
||||||
if param.get("type") == "information":
|
if param.get("type") == "information":
|
||||||
if param["variable"]:
|
if param["variable"]:
|
||||||
variable_path = self.get_realpath(param["variable"])
|
if self.ori_path is None:
|
||||||
param["variable"] = objectspace.paths[variable_path]
|
path = self.path
|
||||||
if not param["variable"]:
|
else:
|
||||||
raise Exception("pffff")
|
path = self.ori_path
|
||||||
|
variable, suffix = objectspace.paths.get_with_dynamic(
|
||||||
|
param["variable"],
|
||||||
|
self.path_prefix,
|
||||||
|
path,
|
||||||
|
self.version,
|
||||||
|
self.namespace,
|
||||||
|
self.xmlfiles,
|
||||||
|
)
|
||||||
|
if not variable:
|
||||||
|
msg = f'cannot find variable "{param["variable"]}" defined in "{self.attribute_name}" for "{self.path}"'
|
||||||
|
raise DictConsistencyError(msg, 14, self.xmlfiles)
|
||||||
|
param["variable"] = variable
|
||||||
|
if suffix:
|
||||||
|
msg = f'variable "{param["variable"]}" defined in "{self.attribute_name}" for "{self.path}" is a dynamic variable'
|
||||||
|
raise DictConsistencyError(msg, 15, self.xmlfiles)
|
||||||
else:
|
else:
|
||||||
del param["variable"]
|
del param["variable"]
|
||||||
params[param.pop("key")] = param
|
params[param.pop("key")] = param
|
||||||
|
@ -174,11 +256,20 @@ class Calculation(BaseModel):
|
||||||
|
|
||||||
class JinjaCalculation(Calculation):
|
class JinjaCalculation(Calculation):
|
||||||
attribute_name: Literal[
|
attribute_name: Literal[
|
||||||
"frozen", "hidden", "mandatory", "disabled", "default", "validators", "choices"
|
"frozen",
|
||||||
|
"hidden",
|
||||||
|
"mandatory",
|
||||||
|
"empty",
|
||||||
|
"disabled",
|
||||||
|
"default",
|
||||||
|
"validators",
|
||||||
|
"choices",
|
||||||
|
"dynamic",
|
||||||
]
|
]
|
||||||
jinja: StrictStr
|
jinja: StrictStr
|
||||||
params: Optional[List[Param]] = None
|
params: Optional[List[Param]] = None
|
||||||
return_type: BASETYPE = None
|
return_type: BASETYPE = None
|
||||||
|
description: Optional[StrictStr] = None
|
||||||
|
|
||||||
def _jinja_to_function(
|
def _jinja_to_function(
|
||||||
self,
|
self,
|
||||||
|
@ -203,29 +294,47 @@ class JinjaCalculation(Calculation):
|
||||||
"__internal_jinja": jinja_path,
|
"__internal_jinja": jinja_path,
|
||||||
"__internal_type": return_type,
|
"__internal_type": return_type,
|
||||||
"__internal_multi": multi,
|
"__internal_multi": multi,
|
||||||
|
"__internal_files": self.xmlfiles,
|
||||||
|
"__internal_attribute": self.attribute_name,
|
||||||
|
"__internal_variable": self.path,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
if self.default_values:
|
||||||
|
default["params"]["__default_value"] = self.default_values
|
||||||
if add_help:
|
if add_help:
|
||||||
default["help"] = function + "_help"
|
default["help"] = function + "_help"
|
||||||
if self.params:
|
if self.params:
|
||||||
default["params"] |= self.get_params(objectspace)
|
default["params"] |= self.get_params(objectspace)
|
||||||
if params:
|
if params:
|
||||||
default["params"] |= params
|
default["params"] |= params
|
||||||
for sub_variable, suffix, true_path, dynamic in get_jinja_variable_to_param(
|
if self.ori_path is None:
|
||||||
|
path = self.path
|
||||||
|
else:
|
||||||
|
path = self.ori_path
|
||||||
|
for sub_variable, suffix, true_path in get_jinja_variable_to_param(
|
||||||
|
path,
|
||||||
self.jinja,
|
self.jinja,
|
||||||
objectspace,
|
objectspace,
|
||||||
variable.xmlfiles,
|
variable.xmlfiles,
|
||||||
objectspace.functions,
|
objectspace.functions,
|
||||||
self.path_prefix,
|
self.path_prefix,
|
||||||
|
self.version,
|
||||||
|
self.namespace,
|
||||||
):
|
):
|
||||||
if sub_variable.path in objectspace.variables:
|
if true_path in default["params"]:
|
||||||
|
continue
|
||||||
|
if isinstance(sub_variable, dict):
|
||||||
|
default["params"][true_path] = {
|
||||||
|
"type": "value",
|
||||||
|
"value": sub_variable,
|
||||||
|
}
|
||||||
|
else:
|
||||||
default["params"][true_path] = {
|
default["params"][true_path] = {
|
||||||
"type": "variable",
|
"type": "variable",
|
||||||
"variable": sub_variable,
|
"variable": sub_variable,
|
||||||
}
|
}
|
||||||
if suffix:
|
if suffix:
|
||||||
default["params"][true_path]["suffix"] = suffix
|
default["params"][true_path]["suffix"] = suffix
|
||||||
default["params"][true_path]["dynamic"] = dynamic
|
|
||||||
return default
|
return default
|
||||||
|
|
||||||
def to_function(
|
def to_function(
|
||||||
|
@ -258,7 +367,7 @@ class JinjaCalculation(Calculation):
|
||||||
False,
|
False,
|
||||||
objectspace,
|
objectspace,
|
||||||
)
|
)
|
||||||
elif self.attribute_name in ["frozen", "hidden", "disabled", "mandatory"]:
|
elif self.attribute_name in PROPERTY_ATTRIBUTE:
|
||||||
if self.return_type:
|
if self.return_type:
|
||||||
raise Exception("return_type not allowed!")
|
raise Exception("return_type not allowed!")
|
||||||
return self._jinja_to_function(
|
return self._jinja_to_function(
|
||||||
|
@ -267,7 +376,7 @@ class JinjaCalculation(Calculation):
|
||||||
False,
|
False,
|
||||||
objectspace,
|
objectspace,
|
||||||
add_help=True,
|
add_help=True,
|
||||||
params={None: [self.attribute_name]},
|
params={None: [self.attribute_name], "when": True, "inverse": False},
|
||||||
)
|
)
|
||||||
elif self.attribute_name == "choices":
|
elif self.attribute_name == "choices":
|
||||||
return_type = self.return_type
|
return_type = self.return_type
|
||||||
|
@ -279,26 +388,52 @@ class JinjaCalculation(Calculation):
|
||||||
not self.inside_list,
|
not self.inside_list,
|
||||||
objectspace,
|
objectspace,
|
||||||
)
|
)
|
||||||
|
elif self.attribute_name == "dynamic":
|
||||||
|
return self._jinja_to_function(
|
||||||
|
"jinja_to_function",
|
||||||
|
"string",
|
||||||
|
True,
|
||||||
|
objectspace,
|
||||||
|
)
|
||||||
raise Exception("hu?")
|
raise Exception("hu?")
|
||||||
|
|
||||||
|
|
||||||
class VariableCalculation(Calculation):
|
class _VariableCalculation(Calculation):
|
||||||
attribute_name: Literal[
|
|
||||||
"frozen", "hidden", "mandatory", "disabled", "default", "choices"
|
|
||||||
]
|
|
||||||
variable: StrictStr
|
variable: StrictStr
|
||||||
propertyerror: bool = True
|
propertyerror: bool = True
|
||||||
|
allow_none: bool = False
|
||||||
|
|
||||||
def to_function(
|
def get_variable(self,
|
||||||
|
objectspace,
|
||||||
|
) -> "Variable":
|
||||||
|
if self.ori_path is None:
|
||||||
|
path = self.path
|
||||||
|
else:
|
||||||
|
path = self.ori_path
|
||||||
|
variable, suffix = objectspace.paths.get_with_dynamic(
|
||||||
|
self.variable,
|
||||||
|
self.path_prefix,
|
||||||
|
path,
|
||||||
|
self.version,
|
||||||
|
self.namespace,
|
||||||
|
self.xmlfiles,
|
||||||
|
)
|
||||||
|
if variable and not isinstance(variable, objectspace.variable):
|
||||||
|
# FIXME remove the pfff
|
||||||
|
raise Exception("pfff it's a family")
|
||||||
|
return variable, suffix
|
||||||
|
|
||||||
|
def get_params(
|
||||||
self,
|
self,
|
||||||
objectspace,
|
objectspace,
|
||||||
) -> dict:
|
variable: "Variable",
|
||||||
variable_path = self.get_realpath(self.variable)
|
suffix: Optional[str],
|
||||||
variable, suffix, dynamic = objectspace.paths.get_with_dynamic(variable_path)
|
*,
|
||||||
|
needs_multi: Optional[bool] = None,
|
||||||
|
):
|
||||||
if not variable:
|
if not variable:
|
||||||
raise Exception(f"pffff {variable_path}")
|
msg = f'Variable not found "{self.variable}" for attribut "{self.attribute_name}" for variable "{self.path}"'
|
||||||
if not isinstance(variable, objectspace.variable):
|
raise DictConsistencyError(msg, 88, self.xmlfiles)
|
||||||
raise Exception("pfff it's a family")
|
|
||||||
param = {
|
param = {
|
||||||
"type": "variable",
|
"type": "variable",
|
||||||
"variable": variable,
|
"variable": variable,
|
||||||
|
@ -306,36 +441,124 @@ class VariableCalculation(Calculation):
|
||||||
}
|
}
|
||||||
if suffix:
|
if suffix:
|
||||||
param["suffix"] = suffix
|
param["suffix"] = suffix
|
||||||
param["dynamic"] = dynamic
|
|
||||||
params = {None: [param]}
|
params = {None: [param]}
|
||||||
function = "calc_value"
|
if self.default_values:
|
||||||
help_function = None
|
params["__default_value"] = self.default_values
|
||||||
if self.attribute_name in ["frozen", "hidden", "disabled", "mandatory"]:
|
if self.allow_none:
|
||||||
function = "variable_to_property"
|
|
||||||
help_function = "variable_to_property"
|
|
||||||
if variable.type != "boolean":
|
|
||||||
raise Exception("only boolean!")
|
|
||||||
params[None].insert(0, self.attribute_name)
|
|
||||||
if not self.inside_list and self.path in objectspace.multis:
|
|
||||||
if (
|
|
||||||
not objectspace.paths.is_dynamic(variable_path)
|
|
||||||
and variable_path not in objectspace.multis
|
|
||||||
):
|
|
||||||
params["multi"] = True
|
|
||||||
params["allow_none"] = True
|
params["allow_none"] = True
|
||||||
if self.inside_list and variable.path in objectspace.multis:
|
if needs_multi is None:
|
||||||
raise Exception("pfff")
|
if self.attribute_name != "default":
|
||||||
ret = {
|
needs_multi = True
|
||||||
"function": function,
|
else:
|
||||||
|
needs_multi = self.path in objectspace.multis
|
||||||
|
calc_variable_is_multi = variable.path in objectspace.multis
|
||||||
|
if not calc_variable_is_multi:
|
||||||
|
if variable.path in objectspace.paths._dynamics and (
|
||||||
|
suffix is None or suffix[-1] is None
|
||||||
|
):
|
||||||
|
self_dyn_path = objectspace.paths._dynamics.get(self.path)
|
||||||
|
if self_dyn_path is not None:
|
||||||
|
var_dyn_path = objectspace.paths._dynamics[variable.path]
|
||||||
|
if self_dyn_path != var_dyn_path and not self_dyn_path.startswith(
|
||||||
|
f"{var_dyn_path}."
|
||||||
|
):
|
||||||
|
calc_variable_is_multi = True
|
||||||
|
else:
|
||||||
|
calc_variable_is_multi = True
|
||||||
|
elif suffix and '{{ suffix }}' in suffix:
|
||||||
|
calc_variable_is_multi = True
|
||||||
|
if needs_multi:
|
||||||
|
if calc_variable_is_multi:
|
||||||
|
if self.inside_list:
|
||||||
|
msg = f'the variable "{self.path}" has an invalid attribute "{self.attribute_name}", the variable "{variable.path}" is multi but is inside a list'
|
||||||
|
raise DictConsistencyError(msg, 18, self.xmlfiles)
|
||||||
|
elif not self.inside_list:
|
||||||
|
msg = f'the variable "{self.path}" has an invalid attribute "{self.attribute_name}", the variable "{variable.path}" is not multi but is not inside a list'
|
||||||
|
raise DictConsistencyError(msg, 20, self.xmlfiles)
|
||||||
|
elif self.inside_list:
|
||||||
|
msg = f'the variable "{self.path}" has an invalid attribute "{self.attribute_name}", it\'s a list'
|
||||||
|
raise DictConsistencyError(msg, 23, self.xmlfiles)
|
||||||
|
elif calc_variable_is_multi:
|
||||||
|
if variable.multi or variable.path.rsplit('.', 1)[0] != self.path.rsplit('.', 1)[0]:
|
||||||
|
# it's not a follower or not in same leadership
|
||||||
|
msg = f'the variable "{self.path}" has an invalid attribute "{self.attribute_name}", the variable "{variable.path}" is a multi'
|
||||||
|
raise DictConsistencyError(msg, 21, self.xmlfiles)
|
||||||
|
else:
|
||||||
|
params[None][0]['index'] = {'index': {'type': 'index'}}
|
||||||
|
return params
|
||||||
|
|
||||||
|
|
||||||
|
class VariableCalculation(_VariableCalculation):
|
||||||
|
attribute_name: Literal["default", "choices", "dynamic"]
|
||||||
|
optional: bool = False
|
||||||
|
|
||||||
|
def to_function(
|
||||||
|
self,
|
||||||
|
objectspace,
|
||||||
|
) -> dict:
|
||||||
|
if self.attribute_name != "default" and self.optional:
|
||||||
|
msg = f'"{self.attribute_name}" variable shall not have an "optional" attribute for variable "{self.variable}"'
|
||||||
|
raise DictConsistencyError(msg, 33, self.xmlfiles)
|
||||||
|
variable, suffix = self.get_variable(objectspace)
|
||||||
|
if not variable and self.optional:
|
||||||
|
raise VariableCalculationDependencyError()
|
||||||
|
params = self.get_params(objectspace,
|
||||||
|
variable,
|
||||||
|
suffix,
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"function": "calc_value",
|
||||||
"params": params,
|
"params": params,
|
||||||
}
|
}
|
||||||
if help_function:
|
|
||||||
ret["help"] = help_function
|
|
||||||
return ret
|
class VariablePropertyCalculation(_VariableCalculation):
|
||||||
|
attribute_name: Literal[*PROPERTY_ATTRIBUTE]
|
||||||
|
when: Any = undefined
|
||||||
|
when_not: Any = undefined
|
||||||
|
|
||||||
|
def to_function(
|
||||||
|
self,
|
||||||
|
objectspace,
|
||||||
|
) -> dict:
|
||||||
|
variable, suffix = self.get_variable(objectspace)
|
||||||
|
params = self.get_params(objectspace,
|
||||||
|
variable,
|
||||||
|
suffix,
|
||||||
|
needs_multi=False,)
|
||||||
|
variable = params[None][0]["variable"]
|
||||||
|
if self.when is not undefined:
|
||||||
|
if self.version == "1.0":
|
||||||
|
msg = f'when is not allowed in format version 1.0 for attribute "{self.attribute_name}" for variable "{self.path}"'
|
||||||
|
raise DictConsistencyError(msg, 103, variable.xmlfiles)
|
||||||
|
if self.when_not is not undefined:
|
||||||
|
msg = f'the variable "{self.path}" has an invalid attribute "{self.attribute_name}", when and when_not cannot set together'
|
||||||
|
raise DictConsistencyError(msg, 31, variable.xmlfiles)
|
||||||
|
when = self.when
|
||||||
|
inverse = False
|
||||||
|
elif self.when_not is not undefined:
|
||||||
|
if self.version == "1.0":
|
||||||
|
msg = f'when_not is not allowed in format version 1.0 for attribute "{self.attribute_name}" for variable "{self.path}"'
|
||||||
|
raise DictConsistencyError(msg, 104, variable.xmlfiles)
|
||||||
|
when = self.when_not
|
||||||
|
inverse = True
|
||||||
|
else:
|
||||||
|
if variable.type != "boolean":
|
||||||
|
raise Exception("only boolean!")
|
||||||
|
when = True
|
||||||
|
inverse = False
|
||||||
|
params[None].insert(0, self.attribute_name)
|
||||||
|
params["when"] = when
|
||||||
|
params["inverse"] = inverse
|
||||||
|
return {
|
||||||
|
"function": "variable_to_property",
|
||||||
|
"params": params,
|
||||||
|
"help": "variable_to_property",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class InformationCalculation(Calculation):
|
class InformationCalculation(Calculation):
|
||||||
attribute_name: Literal["default"]
|
attribute_name: Literal["default", "choice", "dynamic"]
|
||||||
information: StrictStr
|
information: StrictStr
|
||||||
variable: Optional[StrictStr]
|
variable: Optional[StrictStr]
|
||||||
|
|
||||||
|
@ -343,42 +566,109 @@ class InformationCalculation(Calculation):
|
||||||
self,
|
self,
|
||||||
objectspace,
|
objectspace,
|
||||||
) -> dict:
|
) -> dict:
|
||||||
param = {
|
params = {
|
||||||
"type": "information",
|
None: [
|
||||||
"information": self.information,
|
{
|
||||||
|
"type": "information",
|
||||||
|
"information": self.information,
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
if self.variable:
|
if self.variable:
|
||||||
variable_path = self.get_realpath(self.variable)
|
if self.ori_path is None:
|
||||||
variable = objectspace.paths[variable_path]
|
path = self.path
|
||||||
if variable is None:
|
else:
|
||||||
|
path = self.ori_path
|
||||||
|
variable, suffix = objectspace.paths.get_with_dynamic(
|
||||||
|
self.variable,
|
||||||
|
self.path_prefix,
|
||||||
|
path,
|
||||||
|
self.version,
|
||||||
|
self.namespace,
|
||||||
|
self.xmlfiles,
|
||||||
|
)
|
||||||
|
if variable is None or suffix is not None:
|
||||||
raise Exception("pfff")
|
raise Exception("pfff")
|
||||||
param["variable"] = variable
|
params[None][0]["variable"] = variable
|
||||||
|
if self.default_values:
|
||||||
|
params["__default_value"] = self.default_values
|
||||||
return {
|
return {
|
||||||
"function": "calc_value",
|
"function": "calc_value",
|
||||||
"params": {None: [param]},
|
"params": params,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class SuffixCalculation(Calculation):
|
class _SuffixCalculation(Calculation):
|
||||||
attribute_name: Literal["default"]
|
suffix: Optional[int] = None
|
||||||
|
|
||||||
|
def get_suffix(self) -> dict:
|
||||||
|
suffix = {"type": "suffix"}
|
||||||
|
if self.suffix is not None:
|
||||||
|
suffix["suffix"] = self.suffix
|
||||||
|
return suffix
|
||||||
|
|
||||||
|
|
||||||
|
class SuffixCalculation(_SuffixCalculation):
|
||||||
|
attribute_name: Literal["default", "choice", "dynamic"]
|
||||||
|
|
||||||
def to_function(
|
def to_function(
|
||||||
self,
|
self,
|
||||||
objectspace,
|
objectspace,
|
||||||
) -> dict:
|
) -> dict:
|
||||||
|
suffix = {"type": "suffix"}
|
||||||
|
if self.suffix is not None:
|
||||||
|
suffix["suffix"] = self.suffix
|
||||||
return {
|
return {
|
||||||
"function": "calc_value",
|
"function": "calc_value",
|
||||||
"params": {None: [{"type": "suffix"}]},
|
"params": {None: [self.get_suffix()]},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class SuffixPropertyCalculation(_SuffixCalculation):
|
||||||
|
attribute_name: Literal[*PROPERTY_ATTRIBUTE]
|
||||||
|
when: Any = undefined
|
||||||
|
when_not: Any = undefined
|
||||||
|
|
||||||
|
def to_function(
|
||||||
|
self,
|
||||||
|
objectspace,
|
||||||
|
) -> dict:
|
||||||
|
if self.version == "1.0":
|
||||||
|
msg = f'when is not allowed in format version 1.0 for attribute "{self.attribute_name}"'
|
||||||
|
raise DictConsistencyError(msg, 105, variable.xmlfiles)
|
||||||
|
if self.when is not undefined:
|
||||||
|
if self.when_not is not undefined:
|
||||||
|
msg = f'the suffix has an invalid attribute "{self.attribute_name}", when and when_not cannot set together'
|
||||||
|
raise DictConsistencyError(msg, 35, variable.xmlfiles)
|
||||||
|
when = self.when
|
||||||
|
inverse = False
|
||||||
|
elif self.when_not is not undefined:
|
||||||
|
when = self.when_not
|
||||||
|
inverse = True
|
||||||
|
else:
|
||||||
|
msg = f'the suffix has an invalid attribute "{self.attribute_name}", when and when_not cannot set together'
|
||||||
|
raise DictConsistencyError
|
||||||
|
params = {None: [self.attribute_name, self.get_suffix()],
|
||||||
|
"when": when,
|
||||||
|
"inverse": inverse,
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
"function": "variable_to_property",
|
||||||
|
"params": params,
|
||||||
|
"help": "variable_to_property",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class IndexCalculation(Calculation):
|
class IndexCalculation(Calculation):
|
||||||
attribute_name: Literal["default"]
|
attribute_name: Literal["default", "choice", "dynamic"]
|
||||||
|
|
||||||
def to_function(
|
def to_function(
|
||||||
self,
|
self,
|
||||||
objectspace,
|
objectspace,
|
||||||
) -> dict:
|
) -> dict:
|
||||||
|
if self.path not in objectspace.followers:
|
||||||
|
msg = f'the variable "{self.path}" is not a follower, so cannot have index type for "{self.attribute_name}"'
|
||||||
|
raise DictConsistencyError(msg, 60, self.xmlfiles)
|
||||||
return {
|
return {
|
||||||
"function": "calc_value",
|
"function": "calc_value",
|
||||||
"params": {None: [{"type": "index"}]},
|
"params": {None: [{"type": "index"}]},
|
||||||
|
@ -392,58 +682,76 @@ CALCULATION_TYPES = {
|
||||||
"suffix": SuffixCalculation,
|
"suffix": SuffixCalculation,
|
||||||
"index": IndexCalculation,
|
"index": IndexCalculation,
|
||||||
}
|
}
|
||||||
BASETYPE_CALC = Union[StrictBool, StrictInt, StrictFloat, StrictStr, None, Calculation]
|
CALCULATION_PROPERTY_TYPES = {
|
||||||
|
"jinja": JinjaCalculation,
|
||||||
|
"variable": VariablePropertyCalculation,
|
||||||
|
"information": InformationCalculation,
|
||||||
|
"suffix": SuffixPropertyCalculation,
|
||||||
|
"index": IndexCalculation,
|
||||||
|
}
|
||||||
|
BASETYPE_CALC = Union[StrictBool, StrictInt, StrictFloat, StrictStr, Calculation, None]
|
||||||
|
|
||||||
|
|
||||||
class Family(BaseModel):
|
class Family(BaseModel):
|
||||||
name: str
|
name: str
|
||||||
description: Optional[str] = None
|
description: Optional[str] = None
|
||||||
type: Literal["family", "leadership", "dynamic"] = "family"
|
type: Literal["family", "leadership", "dynamic"] = "family"
|
||||||
|
path: str
|
||||||
help: Optional[str] = None
|
help: Optional[str] = None
|
||||||
mode: Optional[str] = None
|
mode: Optional[str] = None
|
||||||
hidden: Union[bool, Calculation] = False
|
hidden: Union[bool, Calculation] = False
|
||||||
disabled: Union[bool, Calculation] = False
|
disabled: Union[bool, Calculation] = False
|
||||||
|
namespace: Optional[str]
|
||||||
|
version: str
|
||||||
xmlfiles: List[str] = []
|
xmlfiles: List[str] = []
|
||||||
path: str
|
|
||||||
|
|
||||||
model_config = ConfigDict(extra="forbid", arbitrary_types_allowed=True)
|
model_config = ConfigDict(extra="forbid", arbitrary_types_allowed=True)
|
||||||
|
|
||||||
|
|
||||||
class Dynamic(Family):
|
class Dynamic(Family):
|
||||||
variable: str
|
# None only for format 1.0
|
||||||
|
variable: str = None
|
||||||
|
dynamic: Union[List[Union[StrictStr, Calculation]], Calculation]
|
||||||
|
|
||||||
|
|
||||||
class _Variable(BaseModel):
|
class Variable(BaseModel):
|
||||||
|
# type will be set dynamically in `annotator/value.py`, default is None
|
||||||
|
type: str = None
|
||||||
name: str
|
name: str
|
||||||
description: Optional[str] = None
|
description: Optional[str] = None
|
||||||
default: Union[List[BASETYPE_CALC], BASETYPE_CALC] = None
|
default: Union[List[BASETYPE_CALC], BASETYPE_CALC] = None
|
||||||
|
choices: Optional[Union[List[BASETYPE_CALC], Calculation]] = None
|
||||||
|
regexp: Optional[str] = None
|
||||||
params: Optional[List[Param]] = None
|
params: Optional[List[Param]] = None
|
||||||
validators: Optional[List[Calculation]] = None
|
validators: Optional[List[Calculation]] = None
|
||||||
multi: bool = False
|
multi: Optional[bool] = None
|
||||||
unique: Optional[bool] = None
|
unique: Optional[bool] = None
|
||||||
help: Optional[str] = None
|
help: Optional[str] = None
|
||||||
hidden: Union[bool, Calculation] = False
|
hidden: Union[bool, Calculation] = False
|
||||||
disabled: Union[bool, Calculation] = False
|
disabled: Union[bool, Calculation] = False
|
||||||
mandatory: Union[None, bool, Calculation] = True
|
mandatory: Union[None, bool, Calculation] = True
|
||||||
|
empty: Union[None, bool, Calculation] = True
|
||||||
auto_save: bool = False
|
auto_save: bool = False
|
||||||
mode: Optional[str] = None
|
mode: Optional[str] = None
|
||||||
test: Optional[list] = None
|
test: Optional[list] = None
|
||||||
xmlfiles: List[str] = []
|
examples: Optional[list] = None
|
||||||
path: str
|
path: str
|
||||||
|
namespace: Optional[str]
|
||||||
|
version: str
|
||||||
|
path_prefix: Optional[str]
|
||||||
|
xmlfiles: List[str] = []
|
||||||
|
|
||||||
model_config = ConfigDict(extra="forbid", arbitrary_types_allowed=True)
|
model_config = ConfigDict(extra="forbid", arbitrary_types_allowed=True)
|
||||||
|
|
||||||
|
|
||||||
class Choice(_Variable):
|
|
||||||
type: Literal["choice"] = "choice"
|
|
||||||
choices: Union[List[BASETYPE_CALC], Calculation]
|
|
||||||
|
|
||||||
|
|
||||||
class SymLink(BaseModel):
|
class SymLink(BaseModel):
|
||||||
name: str
|
|
||||||
type: Literal["symlink"] = "symlink"
|
type: Literal["symlink"] = "symlink"
|
||||||
opt: _Variable
|
name: str
|
||||||
xmlfiles: List[str] = []
|
|
||||||
path: str
|
path: str
|
||||||
|
opt: Variable
|
||||||
|
namespace: Optional[str]
|
||||||
|
version: str
|
||||||
|
path_prefix: Optional[str]
|
||||||
|
xmlfiles: List[str] = []
|
||||||
|
|
||||||
model_config = ConfigDict(extra="forbid")
|
model_config = ConfigDict(extra="forbid")
|
||||||
|
|
|
@ -1,527 +0,0 @@
|
||||||
"""Manage path to find objects
|
|
||||||
|
|
||||||
Created by:
|
|
||||||
EOLE (http://eole.orion.education.fr)
|
|
||||||
Copyright (C) 2005-2018
|
|
||||||
|
|
||||||
Forked by:
|
|
||||||
Cadoles (http://www.cadoles.com)
|
|
||||||
Copyright (C) 2019-2021
|
|
||||||
|
|
||||||
Silique (https://www.silique.fr)
|
|
||||||
Copyright (C) 2022-2024
|
|
||||||
|
|
||||||
distribued with GPL-2 or later license
|
|
||||||
|
|
||||||
This program is free software; you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation; either version 2 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program; if not, write to the Free Software
|
|
||||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
||||||
"""
|
|
||||||
from typing import List
|
|
||||||
from .i18n import _
|
|
||||||
from .error import DictConsistencyError
|
|
||||||
from .utils import normalize_family
|
|
||||||
|
|
||||||
|
|
||||||
class Path:
|
|
||||||
"""Helper class to handle the `path` attribute.
|
|
||||||
|
|
||||||
sample: path="creole.general.condition"
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
rougailconfig: "RougailConfig",
|
|
||||||
) -> None:
|
|
||||||
self.variables = {}
|
|
||||||
self.families = {}
|
|
||||||
# self.names = {}
|
|
||||||
self.full_paths_families = {}
|
|
||||||
self.full_paths_variables = {}
|
|
||||||
self.full_dyn_paths_families = {}
|
|
||||||
self.valid_enums = {}
|
|
||||||
self.variable_namespace = rougailconfig["variable_namespace"]
|
|
||||||
self.providers = {}
|
|
||||||
self.suppliers = {}
|
|
||||||
self.list_conditions = {}
|
|
||||||
self.suffix = rougailconfig["suffix"]
|
|
||||||
self.index = 0
|
|
||||||
|
|
||||||
def set_path_prefix(self, prefix: str) -> None:
|
|
||||||
self._path_prefix = prefix
|
|
||||||
if prefix:
|
|
||||||
if None in self.full_paths_families:
|
|
||||||
raise DictConsistencyError(
|
|
||||||
_(f'prefix "{prefix}" cannot be set if a prefix "None" exists'),
|
|
||||||
39,
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
for old_prefix in self.full_paths_families:
|
|
||||||
if old_prefix != None:
|
|
||||||
raise DictConsistencyError(
|
|
||||||
_(f"no prefix cannot be set if a prefix exists"), 84, None
|
|
||||||
)
|
|
||||||
if prefix in self.full_paths_families:
|
|
||||||
raise DictConsistencyError(_(f'prefix "{prefix}" already exists'), 83, None)
|
|
||||||
self.full_paths_families[prefix] = {}
|
|
||||||
self.full_paths_variables[prefix] = {}
|
|
||||||
self.valid_enums[prefix] = {}
|
|
||||||
self.providers[prefix] = {}
|
|
||||||
self.suppliers[prefix] = {}
|
|
||||||
self.list_conditions[prefix] = {}
|
|
||||||
|
|
||||||
def has_path_prefix(self) -> bool:
|
|
||||||
return None not in self.full_paths_families
|
|
||||||
|
|
||||||
def get_path_prefixes(self) -> list:
|
|
||||||
return list(self.full_paths_families)
|
|
||||||
|
|
||||||
def get_path_prefix(self) -> str:
|
|
||||||
return self._path_prefix
|
|
||||||
|
|
||||||
# Family
|
|
||||||
def add_family(
|
|
||||||
self,
|
|
||||||
namespace: str,
|
|
||||||
subpath: str,
|
|
||||||
variableobj: str,
|
|
||||||
is_dynamic: str,
|
|
||||||
force_path_prefix: str = None,
|
|
||||||
) -> str: # pylint: disable=C0111
|
|
||||||
"""Add a new family"""
|
|
||||||
if force_path_prefix is None:
|
|
||||||
force_path_prefix = self._path_prefix
|
|
||||||
path = subpath + "." + variableobj.name
|
|
||||||
if namespace == self.variable_namespace:
|
|
||||||
if variableobj.name in self.full_paths_families[force_path_prefix]:
|
|
||||||
msg = _(f'Duplicate family name "{variableobj.name}"')
|
|
||||||
raise DictConsistencyError(msg, 55, variableobj.xmlfiles)
|
|
||||||
self.full_paths_families[force_path_prefix][variableobj.name] = path
|
|
||||||
if is_dynamic:
|
|
||||||
if subpath in self.full_dyn_paths_families:
|
|
||||||
dyn_subpath = self.full_dyn_paths_families[subpath]
|
|
||||||
else:
|
|
||||||
dyn_subpath = subpath
|
|
||||||
self.full_dyn_paths_families[
|
|
||||||
path
|
|
||||||
] = f"{dyn_subpath}.{variableobj.name}{{suffix}}"
|
|
||||||
if path in self.families:
|
|
||||||
msg = _(f'Duplicate family name "{path}"')
|
|
||||||
raise DictConsistencyError(msg, 37, variableobj.xmlfiles)
|
|
||||||
if path in self.variables:
|
|
||||||
msg = _(f'A variable and a family has the same path "{path}"')
|
|
||||||
raise DictConsistencyError(msg, 56, variableobj.xmlfiles)
|
|
||||||
self.families[path] = dict(
|
|
||||||
name=path,
|
|
||||||
namespace=namespace,
|
|
||||||
variableobj=variableobj,
|
|
||||||
)
|
|
||||||
self.set_name(variableobj, "optiondescription_")
|
|
||||||
variableobj.path = path
|
|
||||||
variableobj.path_prefix = force_path_prefix
|
|
||||||
|
|
||||||
def get_family(
|
|
||||||
self,
|
|
||||||
path: str,
|
|
||||||
current_namespace: str,
|
|
||||||
path_prefix: str,
|
|
||||||
allow_variable_namespace: bool = False,
|
|
||||||
) -> "Family": # pylint: disable=C0111
|
|
||||||
"""Get a family"""
|
|
||||||
if (
|
|
||||||
current_namespace == self.variable_namespace or allow_variable_namespace
|
|
||||||
) and path in self.full_paths_families[path_prefix]:
|
|
||||||
path = self.full_paths_families[path_prefix][path]
|
|
||||||
elif allow_variable_namespace and path_prefix:
|
|
||||||
path = f"{path_prefix}.{path}"
|
|
||||||
if path not in self.families:
|
|
||||||
raise DictConsistencyError(_(f'unknown option "{path}"'), 42, [])
|
|
||||||
dico = self.families[path]
|
|
||||||
if current_namespace != dico["namespace"] and (
|
|
||||||
not allow_variable_namespace or current_namespace != self.variable_namespace
|
|
||||||
):
|
|
||||||
msg = _(
|
|
||||||
f'A family located in the "{dico["namespace"]}" namespace '
|
|
||||||
f'shall not be used in the "{current_namespace}" namespace'
|
|
||||||
)
|
|
||||||
raise DictConsistencyError(msg, 38, [])
|
|
||||||
return dico["variableobj"]
|
|
||||||
|
|
||||||
def _get_dyn_path(
|
|
||||||
self,
|
|
||||||
subpath: str,
|
|
||||||
name: bool,
|
|
||||||
) -> str:
|
|
||||||
if subpath in self.full_dyn_paths_families:
|
|
||||||
subpath = self.full_dyn_paths_families[subpath]
|
|
||||||
path = f"{subpath}.{name}{{suffix}}"
|
|
||||||
else:
|
|
||||||
path = f"{subpath}.{name}"
|
|
||||||
return path
|
|
||||||
|
|
||||||
def set_provider(
|
|
||||||
self,
|
|
||||||
variableobj,
|
|
||||||
name,
|
|
||||||
family,
|
|
||||||
):
|
|
||||||
if not hasattr(variableobj, "provider"):
|
|
||||||
return
|
|
||||||
p_name = "provider:" + variableobj.provider
|
|
||||||
if "." in name:
|
|
||||||
msg = f'provider "{p_name}" not allowed in extra'
|
|
||||||
raise DictConsistencyError(msg, 82, variableobj.xmlfiles)
|
|
||||||
if p_name in self.providers[variableobj.path_prefix]:
|
|
||||||
msg = f'provider "{p_name}" declare multiple time'
|
|
||||||
raise DictConsistencyError(msg, 79, variableobj.xmlfiles)
|
|
||||||
self.providers[variableobj.path_prefix][p_name] = {
|
|
||||||
"path": self._get_dyn_path(
|
|
||||||
family,
|
|
||||||
name,
|
|
||||||
),
|
|
||||||
"option": variableobj,
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_provider(
|
|
||||||
self,
|
|
||||||
name: str,
|
|
||||||
path_prefix: str = None,
|
|
||||||
) -> "self.objectspace.variable":
|
|
||||||
return self.providers[path_prefix][name]["option"]
|
|
||||||
|
|
||||||
def get_providers_path(self, path_prefix=None):
|
|
||||||
if path_prefix:
|
|
||||||
return {
|
|
||||||
name: option["path"].split(".", 1)[-1]
|
|
||||||
for name, option in self.providers[path_prefix].items()
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
name: option["path"] for name, option in self.providers[path_prefix].items()
|
|
||||||
}
|
|
||||||
|
|
||||||
def set_supplier(
|
|
||||||
self,
|
|
||||||
variableobj,
|
|
||||||
name,
|
|
||||||
family,
|
|
||||||
):
|
|
||||||
if not hasattr(variableobj, "supplier"):
|
|
||||||
return
|
|
||||||
s_name = "supplier:" + variableobj.supplier
|
|
||||||
if "." in name:
|
|
||||||
msg = f'supplier "{s_name}" not allowed in extra'
|
|
||||||
raise DictConsistencyError(msg, 82, variableobj.xmlfiles)
|
|
||||||
if s_name in self.suppliers[variableobj.path_prefix]:
|
|
||||||
msg = f'supplier "{s_name}" declare multiple time'
|
|
||||||
raise DictConsistencyError(msg, 79, variableobj.xmlfiles)
|
|
||||||
self.suppliers[variableobj.path_prefix][s_name] = {
|
|
||||||
"path": self._get_dyn_path(family, name),
|
|
||||||
"option": variableobj,
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_supplier(
|
|
||||||
self,
|
|
||||||
name: str,
|
|
||||||
path_prefix: str = None,
|
|
||||||
) -> "self.objectspace.variable":
|
|
||||||
return self.suppliers[path_prefix][name]["option"]
|
|
||||||
|
|
||||||
def get_suppliers_path(self, path_prefix=None):
|
|
||||||
if path_prefix:
|
|
||||||
return {
|
|
||||||
name: option["path"].split(".", 1)[-1]
|
|
||||||
for name, option in self.suppliers[path_prefix].items()
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
name: option["path"] for name, option in self.suppliers[path_prefix].items()
|
|
||||||
}
|
|
||||||
|
|
||||||
# Variable
|
|
||||||
def add_variable(
|
|
||||||
self, # pylint: disable=R0913
|
|
||||||
namespace: str,
|
|
||||||
subpath: str,
|
|
||||||
variableobj: "self.objectspace.variable",
|
|
||||||
is_dynamic: bool = False,
|
|
||||||
is_leader: bool = False,
|
|
||||||
force_path_prefix: str = None,
|
|
||||||
) -> str: # pylint: disable=C0111
|
|
||||||
"""Add a new variable (with path)"""
|
|
||||||
if force_path_prefix is None:
|
|
||||||
force_path_prefix = self._path_prefix
|
|
||||||
path = subpath + "." + variableobj.name
|
|
||||||
if namespace == self.variable_namespace:
|
|
||||||
self.full_paths_variables[force_path_prefix][variableobj.name] = path
|
|
||||||
if path in self.families:
|
|
||||||
msg = _(f'A family and a variable has the same path "{path}"')
|
|
||||||
raise DictConsistencyError(msg, 57, variableobj.xmlfiles)
|
|
||||||
if is_leader:
|
|
||||||
leader = subpath
|
|
||||||
else:
|
|
||||||
leader = None
|
|
||||||
self.variables[path] = dict(
|
|
||||||
name=path,
|
|
||||||
family=subpath,
|
|
||||||
leader=leader,
|
|
||||||
is_dynamic=is_dynamic,
|
|
||||||
variableobj=variableobj,
|
|
||||||
)
|
|
||||||
variableobj.path = path
|
|
||||||
variableobj.path_prefix = force_path_prefix
|
|
||||||
self.set_name(variableobj, "option_")
|
|
||||||
|
|
||||||
def set_name(
|
|
||||||
self,
|
|
||||||
variableobj,
|
|
||||||
option_prefix,
|
|
||||||
):
|
|
||||||
self.index += 1
|
|
||||||
variableobj.reflector_name = f"{option_prefix}{self.index}{self.suffix}"
|
|
||||||
|
|
||||||
def get_variable(
|
|
||||||
self,
|
|
||||||
name: str,
|
|
||||||
namespace: str,
|
|
||||||
xmlfiles: List[str] = [],
|
|
||||||
allow_variable_namespace: bool = False,
|
|
||||||
force_path_prefix: str = None,
|
|
||||||
add_path_prefix: bool = False,
|
|
||||||
) -> "Variable": # pylint: disable=C0111
|
|
||||||
"""Get variable object from a path"""
|
|
||||||
if force_path_prefix is None:
|
|
||||||
force_path_prefix = self._path_prefix
|
|
||||||
try:
|
|
||||||
variable, suffix = self._get_variable(
|
|
||||||
name,
|
|
||||||
namespace,
|
|
||||||
with_suffix=True,
|
|
||||||
xmlfiles=xmlfiles,
|
|
||||||
path_prefix=force_path_prefix,
|
|
||||||
add_path_prefix=add_path_prefix,
|
|
||||||
)
|
|
||||||
except DictConsistencyError as err:
|
|
||||||
if (
|
|
||||||
not allow_variable_namespace
|
|
||||||
or err.errno != 42
|
|
||||||
or namespace == self.variable_namespace
|
|
||||||
):
|
|
||||||
raise err from err
|
|
||||||
variable, suffix = self._get_variable(
|
|
||||||
name,
|
|
||||||
self.variable_namespace,
|
|
||||||
with_suffix=True,
|
|
||||||
xmlfiles=xmlfiles,
|
|
||||||
path_prefix=force_path_prefix,
|
|
||||||
)
|
|
||||||
if suffix:
|
|
||||||
raise DictConsistencyError(_(f"{name} is a dynamic variable"), 36, [])
|
|
||||||
return variable["variableobj"]
|
|
||||||
|
|
||||||
def get_variable_family_path(
|
|
||||||
self,
|
|
||||||
name: str,
|
|
||||||
namespace: str,
|
|
||||||
xmlfiles: List[str] = False,
|
|
||||||
force_path_prefix: str = None,
|
|
||||||
) -> str: # pylint: disable=C0111
|
|
||||||
"""Get the full path of a family"""
|
|
||||||
if force_path_prefix is None:
|
|
||||||
force_path_prefix = self._path_prefix
|
|
||||||
return self._get_variable(
|
|
||||||
name,
|
|
||||||
namespace,
|
|
||||||
xmlfiles=xmlfiles,
|
|
||||||
path_prefix=force_path_prefix,
|
|
||||||
)["family"]
|
|
||||||
|
|
||||||
def get_variable_with_suffix(
|
|
||||||
self,
|
|
||||||
name: str,
|
|
||||||
current_namespace: str,
|
|
||||||
xmlfiles: List[str],
|
|
||||||
path_prefix: str,
|
|
||||||
) -> str: # pylint: disable=C0111
|
|
||||||
"""get full path of a variable"""
|
|
||||||
try:
|
|
||||||
dico, suffix = self._get_variable(
|
|
||||||
name,
|
|
||||||
current_namespace,
|
|
||||||
with_suffix=True,
|
|
||||||
xmlfiles=xmlfiles,
|
|
||||||
path_prefix=path_prefix,
|
|
||||||
add_path_prefix=True,
|
|
||||||
)
|
|
||||||
except DictConsistencyError as err:
|
|
||||||
if err.errno != 42 or current_namespace == self.variable_namespace:
|
|
||||||
raise err from err
|
|
||||||
dico, suffix = self._get_variable(
|
|
||||||
name,
|
|
||||||
self.variable_namespace,
|
|
||||||
with_suffix=True,
|
|
||||||
xmlfiles=xmlfiles,
|
|
||||||
path_prefix=path_prefix,
|
|
||||||
add_path_prefix=True,
|
|
||||||
)
|
|
||||||
namespace = dico["variableobj"].namespace
|
|
||||||
if (
|
|
||||||
namespace not in [self.variable_namespace, "services"]
|
|
||||||
and current_namespace != "services"
|
|
||||||
and current_namespace != namespace
|
|
||||||
):
|
|
||||||
msg = _(
|
|
||||||
f'A variable located in the "{namespace}" namespace shall not be used '
|
|
||||||
f'in the "{current_namespace}" namespace'
|
|
||||||
)
|
|
||||||
raise DictConsistencyError(msg, 41, xmlfiles)
|
|
||||||
return dico["variableobj"], suffix
|
|
||||||
|
|
||||||
def path_is_defined(
|
|
||||||
self,
|
|
||||||
path: str,
|
|
||||||
namespace: str,
|
|
||||||
force_path_prefix: str = None,
|
|
||||||
) -> str: # pylint: disable=C0111
|
|
||||||
"""The path is a valid path"""
|
|
||||||
if namespace == self.variable_namespace:
|
|
||||||
if force_path_prefix is None:
|
|
||||||
force_path_prefix = self._path_prefix
|
|
||||||
return path in self.full_paths_variables[force_path_prefix]
|
|
||||||
return path in self.variables
|
|
||||||
|
|
||||||
def get_path(
|
|
||||||
self,
|
|
||||||
path: str,
|
|
||||||
namespace: str,
|
|
||||||
) -> str:
|
|
||||||
if namespace == self.variable_namespace:
|
|
||||||
if path not in self.full_paths_variables[self._path_prefix]:
|
|
||||||
return None
|
|
||||||
path = self.full_paths_variables[self._path_prefix][path]
|
|
||||||
else:
|
|
||||||
path = f"{self._path_prefix}.{path}"
|
|
||||||
return path
|
|
||||||
|
|
||||||
def is_dynamic(self, variableobj) -> bool:
|
|
||||||
"""This variable is in dynamic family"""
|
|
||||||
return self._get_variable(
|
|
||||||
variableobj.path,
|
|
||||||
variableobj.namespace,
|
|
||||||
path_prefix=variableobj.path_prefix,
|
|
||||||
)["is_dynamic"]
|
|
||||||
|
|
||||||
def is_leader(self, variableobj): # pylint: disable=C0111
|
|
||||||
"""Is the variable is a leader"""
|
|
||||||
path = variableobj.path
|
|
||||||
variable = self._get_variable(
|
|
||||||
path,
|
|
||||||
variableobj.namespace,
|
|
||||||
path_prefix=variableobj.path_prefix,
|
|
||||||
)
|
|
||||||
if not variable["leader"]:
|
|
||||||
return False
|
|
||||||
leadership = self.get_family(
|
|
||||||
variable["leader"],
|
|
||||||
variableobj.namespace,
|
|
||||||
path_prefix=variableobj.path_prefix,
|
|
||||||
)
|
|
||||||
return next(iter(leadership.variable.values())).path == path
|
|
||||||
|
|
||||||
def is_follower(self, variableobj) -> bool:
|
|
||||||
"""Is the variable is a follower"""
|
|
||||||
variable = self._get_variable(
|
|
||||||
variableobj.path,
|
|
||||||
variableobj.namespace,
|
|
||||||
path_prefix=variableobj.path_prefix,
|
|
||||||
)
|
|
||||||
if not variable["leader"]:
|
|
||||||
return False
|
|
||||||
leadership = self.get_family(
|
|
||||||
variable["leader"],
|
|
||||||
variableobj.namespace,
|
|
||||||
path_prefix=variableobj.path_prefix,
|
|
||||||
)
|
|
||||||
return next(iter(leadership.variable.values())).path != variableobj.path
|
|
||||||
|
|
||||||
def get_leader(self, variableobj) -> str:
|
|
||||||
variable = self._get_variable(
|
|
||||||
variableobj.path,
|
|
||||||
variableobj.namespace,
|
|
||||||
path_prefix=variableobj.path_prefix,
|
|
||||||
)
|
|
||||||
if not variable["leader"]:
|
|
||||||
raise Exception(f"cannot find leader for {variableobj.path}")
|
|
||||||
leadership = self.get_family(
|
|
||||||
variable["leader"],
|
|
||||||
variableobj.namespace,
|
|
||||||
path_prefix=variableobj.path_prefix,
|
|
||||||
)
|
|
||||||
return next(iter(leadership.variable.values()))
|
|
||||||
|
|
||||||
def _get_variable(
|
|
||||||
self,
|
|
||||||
path: str,
|
|
||||||
namespace: str,
|
|
||||||
with_suffix: bool = False,
|
|
||||||
xmlfiles: List[str] = [],
|
|
||||||
path_prefix: str = None,
|
|
||||||
add_path_prefix: bool = False,
|
|
||||||
) -> str:
|
|
||||||
if namespace == self.variable_namespace:
|
|
||||||
if path in self.full_paths_variables[path_prefix]:
|
|
||||||
path = self.full_paths_variables[path_prefix][path]
|
|
||||||
else:
|
|
||||||
if with_suffix:
|
|
||||||
for var_name, full_path in self.full_paths_variables[
|
|
||||||
path_prefix
|
|
||||||
].items():
|
|
||||||
if not path.startswith(var_name):
|
|
||||||
continue
|
|
||||||
variable = self._get_variable(
|
|
||||||
full_path, namespace, path_prefix=path_prefix
|
|
||||||
)
|
|
||||||
if not variable["is_dynamic"]:
|
|
||||||
continue
|
|
||||||
return variable, path[len(var_name) :]
|
|
||||||
if path_prefix and add_path_prefix:
|
|
||||||
path = f"{path_prefix}.{path}"
|
|
||||||
elif path_prefix and add_path_prefix:
|
|
||||||
path = f"{path_prefix}.{path}"
|
|
||||||
# FIXME with_suffix and variable in extra?
|
|
||||||
if path not in self.variables:
|
|
||||||
raise DictConsistencyError(_(f'unknown option "{path}"'), 42, xmlfiles)
|
|
||||||
if with_suffix:
|
|
||||||
return self.variables[path], None
|
|
||||||
return self.variables[path]
|
|
||||||
|
|
||||||
def set_valid_enums(
|
|
||||||
self,
|
|
||||||
path,
|
|
||||||
values,
|
|
||||||
path_prefix,
|
|
||||||
):
|
|
||||||
self.valid_enums[path_prefix][path] = values
|
|
||||||
|
|
||||||
def has_valid_enums(
|
|
||||||
self,
|
|
||||||
path: str,
|
|
||||||
path_prefix: str,
|
|
||||||
) -> bool:
|
|
||||||
return path in self.valid_enums[path_prefix]
|
|
||||||
|
|
||||||
def get_valid_enums(
|
|
||||||
self,
|
|
||||||
path: str,
|
|
||||||
path_prefix: str,
|
|
||||||
):
|
|
||||||
return self.valid_enums[path_prefix][path]
|
|
|
@ -1,220 +0,0 @@
|
||||||
"""load XML and YAML file from directory
|
|
||||||
|
|
||||||
Created by:
|
|
||||||
EOLE (http://eole.orion.education.fr)
|
|
||||||
Copyright (C) 2005-2018
|
|
||||||
|
|
||||||
Forked by:
|
|
||||||
Cadoles (http://www.cadoles.com)
|
|
||||||
Copyright (C) 2019-2021
|
|
||||||
|
|
||||||
Silique (https://www.silique.fr)
|
|
||||||
Copyright (C) 2022-2024
|
|
||||||
|
|
||||||
distribued with GPL-2 or later license
|
|
||||||
|
|
||||||
This program is free software; you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation; either version 2 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program; if not, write to the Free Software
|
|
||||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
||||||
"""
|
|
||||||
from typing import List
|
|
||||||
from os.path import join, isfile
|
|
||||||
from os import listdir
|
|
||||||
|
|
||||||
from lxml.etree import DTD, parse, XMLSyntaxError # pylint: disable=E0611
|
|
||||||
from pykwalify.compat import yml
|
|
||||||
from pykwalify.core import Core
|
|
||||||
from pykwalify.errors import SchemaError
|
|
||||||
|
|
||||||
|
|
||||||
from .i18n import _
|
|
||||||
from .error import DictConsistencyError
|
|
||||||
|
|
||||||
|
|
||||||
FORCE_SUBYAML = ["override"]
|
|
||||||
SCHEMA_DATA = {}
|
|
||||||
|
|
||||||
|
|
||||||
class Reflector:
|
|
||||||
"""Helper class for loading the Creole XML file,
|
|
||||||
parsing it, validating against the Creole DTD
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
rougailconfig: "RougailConfig",
|
|
||||||
) -> None:
|
|
||||||
"""Loads the Creole DTD
|
|
||||||
|
|
||||||
:raises IOError: if the DTD is not found
|
|
||||||
|
|
||||||
:param dtdfilename: the full filename of the Creole DTD
|
|
||||||
"""
|
|
||||||
dtdfilename = rougailconfig["dtdfilename"]
|
|
||||||
yamlschema_filename = rougailconfig["yamlschema_filename"]
|
|
||||||
if not isfile(dtdfilename):
|
|
||||||
raise IOError(_(f"no such DTD file: {dtdfilename}"))
|
|
||||||
with open(dtdfilename, "r") as dtdfd:
|
|
||||||
self.dtd = DTD(dtdfd)
|
|
||||||
if not isfile(yamlschema_filename):
|
|
||||||
raise IOError(_(f"no such YAML Schema file: {yamlschema_filename}"))
|
|
||||||
self.yamlschema_filename = yamlschema_filename
|
|
||||||
self.schema_data = None
|
|
||||||
|
|
||||||
def load_dictionaries_from_folders(
|
|
||||||
self,
|
|
||||||
folders: List[str],
|
|
||||||
just_doc: bool,
|
|
||||||
):
|
|
||||||
"""Loads all the dictionary files located in the folders' list
|
|
||||||
|
|
||||||
:param folders: list of full folder's name
|
|
||||||
"""
|
|
||||||
filenames = {}
|
|
||||||
for folder in folders:
|
|
||||||
for filename in listdir(folder):
|
|
||||||
if filename.endswith(".xml"):
|
|
||||||
ext = "xml"
|
|
||||||
full_filename = join(folder, filename)
|
|
||||||
elif filename.endswith(".yml"):
|
|
||||||
ext = "yml"
|
|
||||||
full_filename = join(folder, filename)
|
|
||||||
else:
|
|
||||||
continue
|
|
||||||
if filename in filenames:
|
|
||||||
raise DictConsistencyError(
|
|
||||||
_(f"duplicate dictionary file name {filename}"),
|
|
||||||
78,
|
|
||||||
[filenames[filename][1], full_filename],
|
|
||||||
)
|
|
||||||
filenames[filename] = (ext, full_filename)
|
|
||||||
if not filenames and not just_doc:
|
|
||||||
raise DictConsistencyError(_("there is no dictionary file"), 77, folders)
|
|
||||||
file_names = list(filenames.keys())
|
|
||||||
file_names.sort()
|
|
||||||
for filename in file_names:
|
|
||||||
ext, filename = filenames[filename]
|
|
||||||
if ext == "xml":
|
|
||||||
yield self.load_xml_file(filename)
|
|
||||||
else:
|
|
||||||
yield self.load_yml_file(filename)
|
|
||||||
|
|
||||||
def load_xml_file(
|
|
||||||
self,
|
|
||||||
filename: str,
|
|
||||||
):
|
|
||||||
try:
|
|
||||||
document = parse(filename)
|
|
||||||
except XMLSyntaxError as err:
|
|
||||||
raise DictConsistencyError(
|
|
||||||
_(f"not a XML file: {err}"), 52, [filename]
|
|
||||||
) from err
|
|
||||||
if not self.dtd.validate(document):
|
|
||||||
dtd_error = self.dtd.error_log.filter_from_errors()[0]
|
|
||||||
msg = _(f"not a valid XML file: {dtd_error}")
|
|
||||||
raise DictConsistencyError(msg, 43, [filename])
|
|
||||||
return filename, document.getroot()
|
|
||||||
|
|
||||||
def load_yml_file(
|
|
||||||
self,
|
|
||||||
filename: str,
|
|
||||||
):
|
|
||||||
global SCHEMA_DATA
|
|
||||||
if self.yamlschema_filename not in SCHEMA_DATA:
|
|
||||||
with open(self.yamlschema_filename, "r") as fh:
|
|
||||||
SCHEMA_DATA[self.yamlschema_filename] = yml.load(fh)
|
|
||||||
try:
|
|
||||||
document = Core(
|
|
||||||
source_file=filename,
|
|
||||||
schema_data=SCHEMA_DATA[self.yamlschema_filename],
|
|
||||||
)
|
|
||||||
except XMLSyntaxError as err:
|
|
||||||
raise DictConsistencyError(
|
|
||||||
_(f"not a XML file: {err}"), 52, [filename]
|
|
||||||
) from err
|
|
||||||
try:
|
|
||||||
return filename, YParser(document.validate(raise_exception=True))
|
|
||||||
except SchemaError as yaml_error:
|
|
||||||
msg = _(f"not a valid YAML file: {yaml_error}")
|
|
||||||
raise DictConsistencyError(msg, 43, [filename])
|
|
||||||
|
|
||||||
|
|
||||||
class SubYAML:
|
|
||||||
def __init__(self, key, value):
|
|
||||||
if value is None:
|
|
||||||
value = {}
|
|
||||||
self.tag = key
|
|
||||||
self.dico = value
|
|
||||||
if "text" in value:
|
|
||||||
self.text = value["text"]
|
|
||||||
else:
|
|
||||||
self.text = None
|
|
||||||
if isinstance(value, list):
|
|
||||||
self.attrib = {}
|
|
||||||
else:
|
|
||||||
self.attrib = {
|
|
||||||
k: v
|
|
||||||
for k, v in value.items()
|
|
||||||
if not isinstance(v, list) and k not in FORCE_SUBYAML
|
|
||||||
}
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"<SubYAML {self.tag} at {id(self)}>"
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
if isinstance(self.dico, list):
|
|
||||||
lists = []
|
|
||||||
for dico in self.dico:
|
|
||||||
for key, value in dico.items():
|
|
||||||
if not isinstance(value, list):
|
|
||||||
value = [value]
|
|
||||||
lists.append((key, value))
|
|
||||||
else:
|
|
||||||
lists = []
|
|
||||||
for key, values in self.dico.items():
|
|
||||||
if key == "variables":
|
|
||||||
for v in values:
|
|
||||||
if "variable" in v:
|
|
||||||
lists.append(("variable", v["variable"]))
|
|
||||||
if "family" in v:
|
|
||||||
lists.append(("family", v["family"]))
|
|
||||||
else:
|
|
||||||
lists.append((key, values))
|
|
||||||
for key, values in lists:
|
|
||||||
if key not in FORCE_SUBYAML and not isinstance(values, list):
|
|
||||||
continue
|
|
||||||
if values is None:
|
|
||||||
values = [None]
|
|
||||||
for value in values:
|
|
||||||
yield SubYAML(key, value)
|
|
||||||
|
|
||||||
def __len__(self):
|
|
||||||
length = 0
|
|
||||||
for _ in self.__iter__():
|
|
||||||
length += 1
|
|
||||||
return length
|
|
||||||
|
|
||||||
|
|
||||||
class YParser:
|
|
||||||
def __init__(self, dico):
|
|
||||||
self.dico = dico
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
for key, values in self.dico.items():
|
|
||||||
if not isinstance(values, list):
|
|
||||||
continue
|
|
||||||
if key == "variables":
|
|
||||||
yield SubYAML(key, values)
|
|
||||||
else:
|
|
||||||
for val in values:
|
|
||||||
yield SubYAML(key, val)
|
|
89
src/rougail/structural_commandline/annotator.py
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
"""Annotate to add specify attribute for tiramisu-cmdline
|
||||||
|
|
||||||
|
Silique (https://www.silique.fr)
|
||||||
|
Copyright (C) 2024
|
||||||
|
|
||||||
|
distribued with GPL-2 or later license
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
"""
|
||||||
|
from rougail.annotator.variable import Walk
|
||||||
|
from rougail.utils import _
|
||||||
|
from rougail.error import DictConsistencyError
|
||||||
|
|
||||||
|
class Annotator(Walk):
|
||||||
|
"""Annotate value"""
|
||||||
|
level = 80
|
||||||
|
|
||||||
|
def __init__(self, objectspace, *args) -> None:
|
||||||
|
if not objectspace.paths:
|
||||||
|
return
|
||||||
|
self.alternative_names = {}
|
||||||
|
self.objectspace = objectspace
|
||||||
|
not_for_commandlines = []
|
||||||
|
for family in self.get_families():
|
||||||
|
if family.commandline:
|
||||||
|
continue
|
||||||
|
self.not_for_commandline(family)
|
||||||
|
not_for_commandlines.append(family.path + '.')
|
||||||
|
for variable in self.get_variables():
|
||||||
|
if variable.type == 'symlink':
|
||||||
|
continue
|
||||||
|
variable_path = variable.path
|
||||||
|
for family_path in not_for_commandlines:
|
||||||
|
if variable_path.startswith(family_path):
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
if not variable.commandline:
|
||||||
|
self.not_for_commandline(variable)
|
||||||
|
else:
|
||||||
|
self.manage_alternative_name(variable)
|
||||||
|
self.manage_negative_description(variable)
|
||||||
|
|
||||||
|
def not_for_commandline(self, variable) -> None:
|
||||||
|
self.objectspace.properties.add(variable.path, 'not_for_commandline', True)
|
||||||
|
|
||||||
|
def manage_alternative_name(self, variable) -> None:
|
||||||
|
if not variable.alternative_name:
|
||||||
|
return
|
||||||
|
alternative_name = variable.alternative_name
|
||||||
|
variable_path = variable.path
|
||||||
|
all_letters = ''
|
||||||
|
for letter in alternative_name:
|
||||||
|
all_letters += letter
|
||||||
|
if all_letters == 'h':
|
||||||
|
msg = _(f'alternative_name "{alternative_name}" conflict with "--help"')
|
||||||
|
raise DictConsistencyError(msg, 202, variable.xmlfiles)
|
||||||
|
if all_letters in self.alternative_names:
|
||||||
|
msg = _(f'conflict alternative_name "{alternative_name}": "{variable_path}" and "{self.alternative_names[all_letters]}"')
|
||||||
|
raise DictConsistencyError(msg, 202, variable.xmlfiles)
|
||||||
|
|
||||||
|
self.alternative_names[alternative_name] = variable_path
|
||||||
|
if '.' not in variable_path:
|
||||||
|
path = alternative_name
|
||||||
|
else:
|
||||||
|
path = variable_path.rsplit('.', 1)[0] + '.' + alternative_name
|
||||||
|
self.objectspace.add_variable(alternative_name, {'type': 'symlink', 'path': path, 'opt': variable}, variable.xmlfiles, False, False, variable.version)
|
||||||
|
|
||||||
|
def manage_negative_description(self, variable) -> None:
|
||||||
|
if not variable.negative_description:
|
||||||
|
if variable.type == 'boolean' and not self.objectspace.add_extra_options:
|
||||||
|
raise DictConsistencyError(_(f'negative_description is mandatory for boolean variable, but "{variable.path}" hasn\'t'), 200, variable.xmlfiles)
|
||||||
|
return
|
||||||
|
if variable.type != 'boolean':
|
||||||
|
raise DictConsistencyError(_(f'negative_description is only available for boolean variable, but "{variable.path}" is "{variable.type}"'), 201, variable.xmlfiles)
|
||||||
|
self.objectspace.informations.add(
|
||||||
|
variable.path, "negative_description", variable.negative_description
|
||||||
|
)
|
42
src/rougail/structural_commandline/config.py
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
"""
|
||||||
|
Config file for Rougail-structural_commandline
|
||||||
|
|
||||||
|
Silique (https://www.silique.fr)
|
||||||
|
Copyright (C) 2024
|
||||||
|
|
||||||
|
distribued with GPL-2 or later license
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
"""
|
||||||
|
def get_rougail_config(*,
|
||||||
|
backward_compatibility=True,
|
||||||
|
) -> dict:
|
||||||
|
options = """
|
||||||
|
structural_commandline:
|
||||||
|
description: Configuration rougail-structural_commandline
|
||||||
|
commandline: false
|
||||||
|
add_extra_options:
|
||||||
|
description: Add extra options to tiramisu-cmdline-parser
|
||||||
|
default: true
|
||||||
|
"""
|
||||||
|
return {'name': 'exporter',
|
||||||
|
'process': 'structural',
|
||||||
|
'options': options,
|
||||||
|
'level': 20,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ('get_rougail_config')
|
||||||
|
|
36
src/rougail/structural_commandline/object_model.py
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
"""Annotate to add specify attribute for tiramisu-cmdline
|
||||||
|
|
||||||
|
Silique (https://www.silique.fr)
|
||||||
|
Copyright (C) 2024
|
||||||
|
|
||||||
|
distribued with GPL-2 or later license
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
"""
|
||||||
|
from typing import Optional
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class Variable(BaseModel):
|
||||||
|
alternative_name: Optional[str]=None
|
||||||
|
commandline: bool=True
|
||||||
|
negative_description: Optional[str]=None
|
||||||
|
|
||||||
|
|
||||||
|
class Family(BaseModel):
|
||||||
|
commandline: bool=True
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ('Variable', 'Family')
|
|
@ -28,12 +28,115 @@ along with this program; if not, write to the Free Software
|
||||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
from tiramisu4 import DynOptionDescription
|
from tiramisu5 import DynOptionDescription, calc_value
|
||||||
except ModuleNotFoundError:
|
except ModuleNotFoundError:
|
||||||
from tiramisu import DynOptionDescription
|
from tiramisu import DynOptionDescription, calc_value
|
||||||
|
from importlib.machinery import SourceFileLoader as _SourceFileLoader
|
||||||
|
from importlib.util import spec_from_loader as _spec_from_loader, module_from_spec as _module_from_spec
|
||||||
|
from jinja2 import StrictUndefined, DictLoader
|
||||||
|
from jinja2.sandbox import SandboxedEnvironment
|
||||||
|
from rougail.object_model import CONVERT_OPTION
|
||||||
|
from rougail.error import display_xmlfiles
|
||||||
|
from tiramisu.error import ValueWarning, ConfigError
|
||||||
from .utils import normalize_family
|
from .utils import normalize_family
|
||||||
|
|
||||||
|
|
||||||
|
global func
|
||||||
|
dict_env = {}
|
||||||
|
ENV = SandboxedEnvironment(loader=DictLoader(dict_env), undefined=StrictUndefined)
|
||||||
|
func = ENV.filters
|
||||||
|
ENV.compile_templates('jinja_caches', zip=None)
|
||||||
|
|
||||||
|
|
||||||
|
def load_functions(path):
|
||||||
|
global _SourceFileLoader, _spec_from_loader, _module_from_spec, func
|
||||||
|
loader = _SourceFileLoader('func', path)
|
||||||
|
spec = _spec_from_loader(loader.name, loader)
|
||||||
|
func_ = _module_from_spec(spec)
|
||||||
|
loader.exec_module(func_)
|
||||||
|
for function in dir(func_):
|
||||||
|
if function.startswith('_'):
|
||||||
|
continue
|
||||||
|
func[function] = getattr(func_, function)
|
||||||
|
|
||||||
|
|
||||||
|
def rougail_calc_value(*args, __default_value=None, **kwargs):
|
||||||
|
values = calc_value(*args, **kwargs)
|
||||||
|
if __default_value is not None and values in [None, []]:
|
||||||
|
return __default_value
|
||||||
|
return values
|
||||||
|
|
||||||
|
|
||||||
|
def jinja_to_function(__internal_variable, __internal_attribute, __internal_jinja, __internal_type, __internal_multi, __internal_files, __default_value=None, **kwargs):
|
||||||
|
global ENV, CONVERT_OPTION
|
||||||
|
kw = {}
|
||||||
|
for key, value in kwargs.items():
|
||||||
|
if '.' in key:
|
||||||
|
c_kw = kw
|
||||||
|
path, var = key.rsplit('.', 1)
|
||||||
|
for subkey in path.split('.'):
|
||||||
|
c_kw = c_kw.setdefault(subkey, {})
|
||||||
|
c_kw[var] = value
|
||||||
|
else:
|
||||||
|
if key in kw:
|
||||||
|
raise ConfigError(f'internal error, multi key for "{key}" in jinja_to_function')
|
||||||
|
kw[key] = value
|
||||||
|
try:
|
||||||
|
values = ENV.get_template(__internal_jinja).render(kw, **func).strip()
|
||||||
|
except Exception as err:
|
||||||
|
raise ConfigError(f'cannot calculating "{__internal_attribute}" attribute for variable "{__internal_variable}" in {display_xmlfiles(__internal_files)}: {err}') from err
|
||||||
|
convert = CONVERT_OPTION[__internal_type].get('func', str)
|
||||||
|
if __internal_multi:
|
||||||
|
values = [convert(val) for val in values.split('\n') if val != ""]
|
||||||
|
if not values and __default_value is not None:
|
||||||
|
return __default_value
|
||||||
|
return values
|
||||||
|
try:
|
||||||
|
values = convert(values)
|
||||||
|
except Exception as err:
|
||||||
|
raise ConfigError(f'cannot converting "{__internal_attribute}" attribute for variable "{__internal_variable}" in {display_xmlfiles(__internal_files)}: {err}') from err
|
||||||
|
values = values if values != '' and values != 'None' else None
|
||||||
|
if values is None and __default_value is not None:
|
||||||
|
return __default_value
|
||||||
|
return values
|
||||||
|
|
||||||
|
|
||||||
|
def variable_to_property(prop, value, when, inverse):
|
||||||
|
if inverse:
|
||||||
|
is_match = value != when
|
||||||
|
else:
|
||||||
|
is_match = value == when
|
||||||
|
return prop if is_match else None
|
||||||
|
|
||||||
|
|
||||||
|
def jinja_to_property(prop, when, inverse, **kwargs):
|
||||||
|
value = func['jinja_to_function'](**kwargs)
|
||||||
|
return func['variable_to_property'](prop, value is not None, when, inverse)
|
||||||
|
|
||||||
|
|
||||||
|
def jinja_to_property_help(prop, **kwargs):
|
||||||
|
value = func['jinja_to_function'](**kwargs)
|
||||||
|
return (prop, f'\"{prop}\" ({value})')
|
||||||
|
|
||||||
|
|
||||||
|
def valid_with_jinja(warnings_only=False, **kwargs):
|
||||||
|
global ValueWarning
|
||||||
|
value = func['jinja_to_function'](**kwargs)
|
||||||
|
if value:
|
||||||
|
if warnings_only:
|
||||||
|
raise ValueWarning(value)
|
||||||
|
else:
|
||||||
|
raise ValueError(value)
|
||||||
|
|
||||||
|
|
||||||
|
func['calc_value'] = rougail_calc_value
|
||||||
|
func['jinja_to_function'] = jinja_to_function
|
||||||
|
func['jinja_to_property'] = jinja_to_property
|
||||||
|
func['jinja_to_property_help'] = jinja_to_property_help
|
||||||
|
func['variable_to_property'] = variable_to_property
|
||||||
|
func['valid_with_jinja'] = valid_with_jinja
|
||||||
|
|
||||||
|
|
||||||
class ConvertDynOptionDescription(DynOptionDescription):
|
class ConvertDynOptionDescription(DynOptionDescription):
|
||||||
"""Suffix could be an integer, we should convert it in str
|
"""Suffix could be an integer, we should convert it in str
|
||||||
Suffix could also contain invalid character, so we should "normalize" it
|
Suffix could also contain invalid character, so we should "normalize" it
|
||||||
|
@ -58,3 +161,12 @@ class ConvertDynOptionDescription(DynOptionDescription):
|
||||||
if "{{ suffix }}" in name:
|
if "{{ suffix }}" in name:
|
||||||
return name.replace("{{ suffix }}", path_suffix)
|
return name.replace("{{ suffix }}", path_suffix)
|
||||||
return name + path_suffix
|
return name + path_suffix
|
||||||
|
|
||||||
|
def impl_get_display_name(
|
||||||
|
self,
|
||||||
|
subconfig,
|
||||||
|
) -> str:
|
||||||
|
display = super().impl_get_display_name(subconfig)
|
||||||
|
if "{{ suffix }}" in display:
|
||||||
|
return display.replace("{{ suffix }}", self.convert_suffix_to_path(self.get_suffixes(subconfig)[-1]))
|
||||||
|
return display
|
||||||
|
|
|
@ -28,12 +28,12 @@ You should have received a copy of the GNU General Public License
|
||||||
along with this program; if not, write to the Free Software
|
along with this program; if not, write to the Free Software
|
||||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
"""
|
"""
|
||||||
from typing import Optional
|
from typing import Optional, Union
|
||||||
from json import dumps
|
from json import dumps
|
||||||
from os.path import isfile, basename
|
from os.path import isfile, basename
|
||||||
|
|
||||||
from .i18n import _
|
from .i18n import _
|
||||||
from .error import DictConsistencyError
|
from .error import DictConsistencyError, VariableCalculationDependencyError
|
||||||
from .utils import normalize_family
|
from .utils import normalize_family
|
||||||
from .object_model import Calculation, CONVERT_OPTION
|
from .object_model import Calculation, CONVERT_OPTION
|
||||||
|
|
||||||
|
@ -59,120 +59,47 @@ class TiramisuReflector:
|
||||||
objectspace,
|
objectspace,
|
||||||
funcs_paths,
|
funcs_paths,
|
||||||
):
|
):
|
||||||
self.rougailconfig = objectspace.rougailconfig
|
self.informations_idx = -1
|
||||||
self.jinja_added = False
|
|
||||||
self.reflector_objects = {}
|
self.reflector_objects = {}
|
||||||
self.text = {
|
self.text = {
|
||||||
"header": [],
|
"header": [],
|
||||||
"option": [],
|
"option": [],
|
||||||
}
|
}
|
||||||
if self.rougailconfig["export_with_import"]:
|
self.objectspace = objectspace
|
||||||
if self.rougailconfig["internal_functions"]:
|
if self.objectspace.export_with_import:
|
||||||
for func in self.rougailconfig["internal_functions"]:
|
if self.objectspace.internal_functions:
|
||||||
|
for func in self.objectspace.internal_functions:
|
||||||
self.text["header"].append(f"func[func] = func")
|
self.text["header"].append(f"func[func] = func")
|
||||||
self.text["header"].extend(
|
self.text["header"].extend(
|
||||||
[
|
[
|
||||||
"from tiramisu import *",
|
"from tiramisu import *",
|
||||||
"from tiramisu.setting import ALLOWED_LEADER_PROPERTIES",
|
"from tiramisu.setting import ALLOWED_LEADER_PROPERTIES",
|
||||||
|
"from re import compile as re_compile",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
if self.objectspace.export_with_import:
|
||||||
|
self.text["header"].extend(
|
||||||
|
[
|
||||||
|
"from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription"
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
for mode in self.rougailconfig["modes_level"]:
|
|
||||||
self.text["header"].append(f'ALLOWED_LEADER_PROPERTIES.add("{mode}")')
|
|
||||||
if funcs_paths:
|
if funcs_paths:
|
||||||
if self.rougailconfig["export_with_import"]:
|
|
||||||
self.text["header"].extend(
|
|
||||||
[
|
|
||||||
"from importlib.machinery import SourceFileLoader as _SourceFileLoader",
|
|
||||||
"from importlib.util import spec_from_loader as _spec_from_loader, module_from_spec as _module_from_spec",
|
|
||||||
"global func",
|
|
||||||
"func = {'calc_value': calc_value}",
|
|
||||||
"",
|
|
||||||
"def _load_functions(path):",
|
|
||||||
" global _SourceFileLoader, _spec_from_loader, _module_from_spec, func",
|
|
||||||
" loader = _SourceFileLoader('func', path)",
|
|
||||||
" spec = _spec_from_loader(loader.name, loader)",
|
|
||||||
" func_ = _module_from_spec(spec)",
|
|
||||||
" loader.exec_module(func_)",
|
|
||||||
" for function in dir(func_):",
|
|
||||||
" if function.startswith('_'):",
|
|
||||||
" continue",
|
|
||||||
" func[function] = getattr(func_, function)",
|
|
||||||
]
|
|
||||||
)
|
|
||||||
for funcs_path in sorted(funcs_paths, key=sorted_func_name):
|
for funcs_path in sorted(funcs_paths, key=sorted_func_name):
|
||||||
if not isfile(funcs_path):
|
if not isfile(funcs_path):
|
||||||
continue
|
continue
|
||||||
self.text["header"].append(f"_load_functions('{funcs_path}')")
|
self.text["header"].append(f"load_functions('{funcs_path}')")
|
||||||
self.objectspace = objectspace
|
if self.objectspace.export_with_import:
|
||||||
|
for mode in self.objectspace.modes_level:
|
||||||
|
self.text["header"].append(f'ALLOWED_LEADER_PROPERTIES.add("{mode}")')
|
||||||
self.make_tiramisu_objects()
|
self.make_tiramisu_objects()
|
||||||
if self.rougailconfig["export_with_import"] and (
|
|
||||||
self.rougailconfig["force_convert_dyn_option_description"]
|
|
||||||
or self.objectspace.has_dyn_option is True
|
|
||||||
):
|
|
||||||
self.text["header"].append(
|
|
||||||
"from rougail.tiramisu import ConvertDynOptionDescription"
|
|
||||||
)
|
|
||||||
for key, value in self.objectspace.jinja.items():
|
for key, value in self.objectspace.jinja.items():
|
||||||
self.add_jinja_to_function(key, value)
|
self.add_jinja_to_function(key, value)
|
||||||
|
|
||||||
def add_jinja_support(self):
|
|
||||||
if not self.jinja_added:
|
|
||||||
self.text["header"].extend(
|
|
||||||
[
|
|
||||||
"from jinja2 import StrictUndefined, DictLoader",
|
|
||||||
"from jinja2.sandbox import SandboxedEnvironment",
|
|
||||||
"from rougail import CONVERT_OPTION",
|
|
||||||
"from tiramisu.error import ValueWarning",
|
|
||||||
"def jinja_to_function(__internal_jinja, __internal_type, __internal_multi, **kwargs):",
|
|
||||||
" global ENV, CONVERT_OPTION",
|
|
||||||
" kw = {}",
|
|
||||||
" for key, value in kwargs.items():",
|
|
||||||
" if '.' in key:",
|
|
||||||
" c_kw = kw",
|
|
||||||
" path, var = key.rsplit('.', 1)",
|
|
||||||
" for subkey in path.split('.'):",
|
|
||||||
" c_kw = c_kw.setdefault(subkey, {})",
|
|
||||||
" c_kw[var] = value",
|
|
||||||
" else:",
|
|
||||||
" kw[key] = value",
|
|
||||||
" values = ENV.get_template(__internal_jinja).render(kw, **func).strip()",
|
|
||||||
" convert = CONVERT_OPTION[__internal_type].get('func', str)",
|
|
||||||
" if __internal_multi:",
|
|
||||||
" return [convert(val) for val in values.split()]",
|
|
||||||
" values = convert(values)",
|
|
||||||
" return values if values != '' and values != 'None' else None",
|
|
||||||
"def variable_to_property(prop, value):",
|
|
||||||
" return prop if value else None",
|
|
||||||
"def jinja_to_property(prop, **kwargs):",
|
|
||||||
" value = func['jinja_to_function'](**kwargs)",
|
|
||||||
" return func['variable_to_property'](prop, value is not None)",
|
|
||||||
"def jinja_to_property_help(prop, **kwargs):",
|
|
||||||
" value = func['jinja_to_function'](**kwargs)",
|
|
||||||
" return (prop, f'\"{prop}\" ({value})')",
|
|
||||||
"def valid_with_jinja(warnings_only=False, **kwargs):",
|
|
||||||
" global ValueWarning",
|
|
||||||
" value = func['jinja_to_function'](**kwargs)",
|
|
||||||
" if value:",
|
|
||||||
" if warnings_only:",
|
|
||||||
" raise ValueWarning(value)",
|
|
||||||
" else:",
|
|
||||||
" raise ValueError(value)",
|
|
||||||
"func['jinja_to_function'] = jinja_to_function",
|
|
||||||
"func['jinja_to_property'] = jinja_to_property",
|
|
||||||
"func['jinja_to_property_help'] = jinja_to_property_help",
|
|
||||||
"func['variable_to_property'] = variable_to_property",
|
|
||||||
"func['valid_with_jinja'] = valid_with_jinja",
|
|
||||||
"dict_env = {}",
|
|
||||||
]
|
|
||||||
)
|
|
||||||
self.jinja_added = True
|
|
||||||
|
|
||||||
def add_jinja_to_function(
|
def add_jinja_to_function(
|
||||||
self,
|
self,
|
||||||
variable_name: str,
|
variable_name: str,
|
||||||
jinja: str,
|
jinja: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.add_jinja_support()
|
|
||||||
jinja_text = dumps(jinja, ensure_ascii=False)
|
jinja_text = dumps(jinja, ensure_ascii=False)
|
||||||
self.text["header"].append(f"dict_env['{variable_name}'] = {jinja_text}")
|
self.text["header"].append(f"dict_env['{variable_name}'] = {jinja_text}")
|
||||||
|
|
||||||
|
@ -181,54 +108,51 @@ class TiramisuReflector:
|
||||||
baseelt = BaseElt()
|
baseelt = BaseElt()
|
||||||
self.objectspace.reflector_names[
|
self.objectspace.reflector_names[
|
||||||
baseelt.path
|
baseelt.path
|
||||||
] = f'option_0{self.rougailconfig["suffix"]}'
|
] = f"option_0{self.objectspace.suffix}"
|
||||||
basefamily = Family(
|
basefamily = Family(
|
||||||
baseelt,
|
baseelt,
|
||||||
self,
|
self,
|
||||||
)
|
)
|
||||||
# FIXMEif not self.objectspace.paths.has_path_prefix():
|
for elt in self.objectspace.paths.get():
|
||||||
if 1:
|
if elt.path in self.objectspace.families:
|
||||||
# for elt in self.reorder_family(self.objectspace.space):
|
Family(
|
||||||
for elt in self.objectspace.paths.get():
|
elt,
|
||||||
if elt.path in self.objectspace.families:
|
|
||||||
Family(
|
|
||||||
elt,
|
|
||||||
self,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
Variable(
|
|
||||||
elt,
|
|
||||||
self,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
path_prefixes = self.objectspace.paths.get_path_prefixes()
|
|
||||||
for path_prefix in path_prefixes:
|
|
||||||
space = self.objectspace.space.variables[path_prefix]
|
|
||||||
self.set_name(space)
|
|
||||||
baseprefix = Family(
|
|
||||||
space,
|
|
||||||
self,
|
self,
|
||||||
)
|
)
|
||||||
basefamily.add(baseprefix)
|
else:
|
||||||
for elt in self.reorder_family(space):
|
Variable(
|
||||||
self.populate_family(
|
elt,
|
||||||
baseprefix,
|
self,
|
||||||
elt,
|
)
|
||||||
)
|
# else:
|
||||||
if not hasattr(baseprefix.elt, "information"):
|
# path_prefixes = self.objectspace.paths.get_path_prefixes()
|
||||||
baseprefix.elt.information = self.objectspace.information(
|
# for path_prefix in path_prefixes:
|
||||||
baseprefix.elt.xmlfiles
|
# space = self.objectspace.space.variables[path_prefix]
|
||||||
)
|
# self.set_name(space)
|
||||||
for key, value in self.objectspace.paths.get_providers_path(
|
# baseprefix = Family(
|
||||||
path_prefix
|
# space,
|
||||||
).items():
|
# self,
|
||||||
setattr(baseprefix.elt.information, key, value)
|
# )
|
||||||
for key, value in self.objectspace.paths.get_suppliers_path(
|
# basefamily.add(baseprefix)
|
||||||
path_prefix
|
# for elt in self.reorder_family(space):
|
||||||
).items():
|
# self.populate_family(
|
||||||
setattr(baseprefix.elt.information, key, value)
|
# baseprefix,
|
||||||
baseelt.name = normalize_family(self.rougailconfig["base_option_name"])
|
# elt,
|
||||||
baseelt.description = self.rougailconfig["base_option_name"]
|
# )
|
||||||
|
# if not hasattr(baseprefix.elt, "information"):
|
||||||
|
# baseprefix.elt.information = self.objectspace.information(
|
||||||
|
# baseprefix.elt.xmlfiles
|
||||||
|
# )
|
||||||
|
# for key, value in self.objectspace.paths.get_providers_path(
|
||||||
|
# path_prefix
|
||||||
|
# ).items():
|
||||||
|
# setattr(baseprefix.elt.information, key, value)
|
||||||
|
# for key, value in self.objectspace.paths.get_suppliers_path(
|
||||||
|
# path_prefix
|
||||||
|
# ).items():
|
||||||
|
# setattr(baseprefix.elt.information, key, value)
|
||||||
|
baseelt.name = normalize_family(self.objectspace.base_option_name)
|
||||||
|
baseelt.description = self.objectspace.base_option_name
|
||||||
self.reflector_objects[baseelt.path].get(
|
self.reflector_objects[baseelt.path].get(
|
||||||
[], baseelt.description
|
[], baseelt.description
|
||||||
) # pylint: disable=E1101
|
) # pylint: disable=E1101
|
||||||
|
@ -242,16 +166,12 @@ class TiramisuReflector:
|
||||||
self.objectspace.set_name(elt, "optiondescription_")
|
self.objectspace.set_name(elt, "optiondescription_")
|
||||||
return self.objectspace.reflector_names[elt.path]
|
return self.objectspace.reflector_names[elt.path]
|
||||||
|
|
||||||
|
def get_information_name(self):
|
||||||
|
self.informations_idx += 1
|
||||||
|
return f"information_{self.informations_idx}"
|
||||||
|
|
||||||
def get_text(self):
|
def get_text(self):
|
||||||
"""Get text"""
|
"""Get text"""
|
||||||
if self.jinja_added:
|
|
||||||
self.text["header"].extend(
|
|
||||||
[
|
|
||||||
"ENV = SandboxedEnvironment(loader=DictLoader(dict_env), undefined=StrictUndefined)",
|
|
||||||
"ENV.filters = func",
|
|
||||||
"ENV.compile_templates('jinja_caches', zip=None)",
|
|
||||||
]
|
|
||||||
)
|
|
||||||
return "\n".join(self.text["header"] + self.text["option"])
|
return "\n".join(self.text["header"] + self.text["option"])
|
||||||
|
|
||||||
|
|
||||||
|
@ -269,19 +189,24 @@ class Common:
|
||||||
self.tiramisu = tiramisu
|
self.tiramisu = tiramisu
|
||||||
tiramisu.reflector_objects[elt.path] = self
|
tiramisu.reflector_objects[elt.path] = self
|
||||||
self.object_type = None
|
self.object_type = None
|
||||||
|
self.informations = []
|
||||||
|
|
||||||
def get(self, calls, parent_name):
|
def get(self, calls, parent_name):
|
||||||
"""Get tiramisu's object"""
|
"""Get tiramisu's object"""
|
||||||
self_calls = calls.copy()
|
if self.elt.path in calls:
|
||||||
if self.elt.path in self_calls:
|
|
||||||
msg = f'"{self.elt.path}" will make an infinite loop'
|
msg = f'"{self.elt.path}" will make an infinite loop'
|
||||||
raise DictConsistencyError(msg, 80, self.elt.xmlfiles)
|
raise DictConsistencyError(msg, 80, self.elt.xmlfiles)
|
||||||
|
self_calls = calls.copy()
|
||||||
self_calls.append(self.elt.path)
|
self_calls.append(self.elt.path)
|
||||||
self.calls = self_calls
|
self.calls = self_calls
|
||||||
if self.option_name is None:
|
if self.option_name is None:
|
||||||
self.option_name = self.objectspace.reflector_names[self.elt.path]
|
self.option_name = self.objectspace.reflector_names[self.elt.path]
|
||||||
self.populate_attrib()
|
self.populate_attrib()
|
||||||
self.populate_informations()
|
if self.informations:
|
||||||
|
for information in self.informations:
|
||||||
|
self.tiramisu.text["option"].append(
|
||||||
|
f"{information}.set_option({self.option_name})"
|
||||||
|
)
|
||||||
return self.option_name
|
return self.option_name
|
||||||
|
|
||||||
def populate_attrib(self):
|
def populate_attrib(self):
|
||||||
|
@ -294,6 +219,7 @@ class Common:
|
||||||
keys["properties"] = self.properties_to_string(
|
keys["properties"] = self.properties_to_string(
|
||||||
self.objectspace.properties[self.elt.path]
|
self.objectspace.properties[self.elt.path]
|
||||||
)
|
)
|
||||||
|
self.populate_informations(keys)
|
||||||
attrib = ", ".join([f"{key}={value}" for key, value in keys.items()])
|
attrib = ", ".join([f"{key}={value}" for key, value in keys.items()])
|
||||||
self.tiramisu.text["option"].append(
|
self.tiramisu.text["option"].append(
|
||||||
f"{self.option_name} = {self.object_type}({attrib})"
|
f"{self.option_name} = {self.object_type}({attrib})"
|
||||||
|
@ -322,12 +248,11 @@ class Common:
|
||||||
for property_, value in values.items():
|
for property_, value in values.items():
|
||||||
if value is True:
|
if value is True:
|
||||||
properties.append(self.convert_str(property_))
|
properties.append(self.convert_str(property_))
|
||||||
|
elif isinstance(value, list):
|
||||||
|
for val in value:
|
||||||
|
calc_properties.append(self.calculation_value(val))
|
||||||
else:
|
else:
|
||||||
if isinstance(value, list):
|
calc_properties.append(self.calculation_value(value))
|
||||||
for val in value:
|
|
||||||
calc_properties.append(self.calculation_value(val))
|
|
||||||
else:
|
|
||||||
calc_properties.append(self.calculation_value(value))
|
|
||||||
return "frozenset({" + ", ".join(sorted(properties) + calc_properties) + "})"
|
return "frozenset({" + ", ".join(sorted(properties) + calc_properties) + "})"
|
||||||
|
|
||||||
def calc_properties(
|
def calc_properties(
|
||||||
|
@ -350,17 +275,12 @@ class Common:
|
||||||
f"kwargs={{{kwargs}}}), func['calc_value_property_help'])"
|
f"kwargs={{{kwargs}}}), func['calc_value_property_help'])"
|
||||||
)
|
)
|
||||||
|
|
||||||
def populate_informations(self):
|
def populate_informations(self, keys):
|
||||||
"""Populate Tiramisu's informations"""
|
"""Populate Tiramisu's informations"""
|
||||||
informations = self.objectspace.informations.get(self.elt.path)
|
informations = self.objectspace.informations.get(self.elt.path)
|
||||||
if not informations:
|
if not informations:
|
||||||
return
|
return
|
||||||
for key, value in informations.items():
|
keys["informations"] = informations
|
||||||
if isinstance(value, str):
|
|
||||||
value = self.convert_str(value)
|
|
||||||
self.tiramisu.text["option"].append(
|
|
||||||
f"{self.option_name}.impl_set_information('{key}', {value})"
|
|
||||||
)
|
|
||||||
|
|
||||||
def populate_param(
|
def populate_param(
|
||||||
self,
|
self,
|
||||||
|
@ -373,7 +293,10 @@ class Common:
|
||||||
else:
|
else:
|
||||||
value = param
|
value = param
|
||||||
return f"ParamValue({value})"
|
return f"ParamValue({value})"
|
||||||
|
if param["type"] == "value":
|
||||||
|
return f"ParamValue({param['value']})"
|
||||||
if param["type"] == "information":
|
if param["type"] == "information":
|
||||||
|
# default? really?
|
||||||
if self.elt.multi:
|
if self.elt.multi:
|
||||||
default = []
|
default = []
|
||||||
else:
|
else:
|
||||||
|
@ -381,9 +304,27 @@ class Common:
|
||||||
if "variable" in param:
|
if "variable" in param:
|
||||||
if param["variable"].path == self.elt.path:
|
if param["variable"].path == self.elt.path:
|
||||||
return f'ParamSelfInformation("{param["information"]}", {default})'
|
return f'ParamSelfInformation("{param["information"]}", {default})'
|
||||||
return f'ParamInformation("{param["information"]}", {default}, option={self.tiramisu.reflector_objects[param["variable"].path].get(self.calls, self.elt.path)})'
|
information_variable_path = param["variable"].path
|
||||||
|
information_variable = self.tiramisu.reflector_objects[
|
||||||
|
information_variable_path
|
||||||
|
]
|
||||||
|
if information_variable_path not in self.calls:
|
||||||
|
option_name = information_variable.get(self.calls, self.elt.path)
|
||||||
|
return f'ParamInformation("{param["information"]}", {default}, option={option_name})'
|
||||||
|
else:
|
||||||
|
information = (
|
||||||
|
f'ParamInformation("{param["information"]}", {default})'
|
||||||
|
)
|
||||||
|
information_name = self.tiramisu.get_information_name()
|
||||||
|
self.tiramisu.text["option"].append(
|
||||||
|
f"{information_name} = {information}"
|
||||||
|
)
|
||||||
|
information_variable.informations.append(information_name)
|
||||||
|
return information_name
|
||||||
return f'ParamInformation("{param["information"]}", {default})'
|
return f'ParamInformation("{param["information"]}", {default})'
|
||||||
if param["type"] == "suffix":
|
if param["type"] == "suffix":
|
||||||
|
if "suffix" in param and param["suffix"] != None:
|
||||||
|
return f"ParamSuffix(suffix_index={param['suffix']})"
|
||||||
return "ParamSuffix()"
|
return "ParamSuffix()"
|
||||||
if param["type"] == "index":
|
if param["type"] == "index":
|
||||||
return "ParamIndex()"
|
return "ParamIndex()"
|
||||||
|
@ -393,6 +334,7 @@ class Common:
|
||||||
param.get("propertyerror", True),
|
param.get("propertyerror", True),
|
||||||
param.get("suffix"),
|
param.get("suffix"),
|
||||||
param.get("dynamic"),
|
param.get("dynamic"),
|
||||||
|
param.get('whole', False),
|
||||||
)
|
)
|
||||||
if param["type"] == "any":
|
if param["type"] == "any":
|
||||||
if isinstance(param["value"], str):
|
if isinstance(param["value"], str):
|
||||||
|
@ -404,24 +346,25 @@ class Common:
|
||||||
|
|
||||||
def build_option_param(
|
def build_option_param(
|
||||||
self,
|
self,
|
||||||
param,
|
variable,
|
||||||
propertyerror,
|
propertyerror,
|
||||||
suffix: Optional[str],
|
suffix: Optional[str],
|
||||||
dynamic,
|
dynamic,
|
||||||
|
whole: bool,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""build variable parameters"""
|
"""build variable parameters"""
|
||||||
if param.path == self.elt.path:
|
if variable.path == self.elt.path:
|
||||||
return "ParamSelfOption(whole=False)"
|
return f"ParamSelfOption(whole={whole})"
|
||||||
option_name = self.tiramisu.reflector_objects[param.path].get(
|
if whole:
|
||||||
|
msg = f'variable param "{variable.path}" has whole attribute but it\'s not allowed for external variable'
|
||||||
|
raise DictConsistencyError(msg, 34, self.elt.xmlfiles)
|
||||||
|
option_name = self.tiramisu.reflector_objects[variable.path].get(
|
||||||
self.calls, self.elt.path
|
self.calls, self.elt.path
|
||||||
)
|
)
|
||||||
params = [f"{option_name}"]
|
params = [f"{option_name}"]
|
||||||
if suffix is not None:
|
if suffix is not None:
|
||||||
param_type = "ParamDynOption"
|
param_type = "ParamDynOption"
|
||||||
family = self.tiramisu.reflector_objects[dynamic.path].get(
|
params.append(self.convert_str(suffix))
|
||||||
self.calls, self.elt.path
|
|
||||||
)
|
|
||||||
params.extend([f"'{suffix}'", f"{family}"])
|
|
||||||
else:
|
else:
|
||||||
param_type = "ParamOption"
|
param_type = "ParamOption"
|
||||||
if not propertyerror:
|
if not propertyerror:
|
||||||
|
@ -433,7 +376,6 @@ class Common:
|
||||||
function,
|
function,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Generate calculated value"""
|
"""Generate calculated value"""
|
||||||
self.tiramisu.add_jinja_support()
|
|
||||||
child = function.to_function(self.objectspace)
|
child = function.to_function(self.objectspace)
|
||||||
new_args = []
|
new_args = []
|
||||||
kwargs = []
|
kwargs = []
|
||||||
|
@ -460,6 +402,41 @@ class Common:
|
||||||
ret = ret + ")"
|
ret = ret + ")"
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
def populate_calculation(
|
||||||
|
self,
|
||||||
|
datas: Union[Calculation, str, list],
|
||||||
|
return_a_tuple: bool = False,
|
||||||
|
) -> str:
|
||||||
|
if isinstance(datas, str):
|
||||||
|
return self.convert_str(datas)
|
||||||
|
if isinstance(datas, Calculation):
|
||||||
|
return self.calculation_value(datas)
|
||||||
|
if not isinstance(datas, list):
|
||||||
|
return datas
|
||||||
|
params = []
|
||||||
|
for idx, data in enumerate(datas):
|
||||||
|
if isinstance(data, Calculation):
|
||||||
|
try:
|
||||||
|
params.append(self.calculation_value(data))
|
||||||
|
except VariableCalculationDependencyError:
|
||||||
|
pass
|
||||||
|
elif isinstance(data, str):
|
||||||
|
params.append(self.convert_str(data))
|
||||||
|
else:
|
||||||
|
params.append(str(data))
|
||||||
|
if return_a_tuple:
|
||||||
|
ret = "("
|
||||||
|
else:
|
||||||
|
ret = "["
|
||||||
|
ret += ", ".join(params)
|
||||||
|
if return_a_tuple:
|
||||||
|
if len(params) <= 1:
|
||||||
|
ret += ","
|
||||||
|
ret += ")"
|
||||||
|
else:
|
||||||
|
ret += "]"
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
class Variable(Common):
|
class Variable(Common):
|
||||||
"""Manage variable"""
|
"""Manage variable"""
|
||||||
|
@ -470,8 +447,8 @@ class Variable(Common):
|
||||||
tiramisu,
|
tiramisu,
|
||||||
):
|
):
|
||||||
super().__init__(elt, tiramisu)
|
super().__init__(elt, tiramisu)
|
||||||
if elt.type in self.tiramisu.objectspace.rougailconfig['custom_types']:
|
if elt.type in self.tiramisu.objectspace.custom_types:
|
||||||
self.object_type = self.tiramisu.objectspace.rougailconfig['custom_types'][elt.type].__name__
|
self.object_type = self.tiramisu.objectspace.custom_types[elt.type].__name__
|
||||||
else:
|
else:
|
||||||
self.object_type = CONVERT_OPTION[elt.type]["opttype"]
|
self.object_type = CONVERT_OPTION[elt.type]["opttype"]
|
||||||
|
|
||||||
|
@ -483,58 +460,39 @@ class Variable(Common):
|
||||||
keys["opt"] = self.tiramisu.reflector_objects[self.elt.opt.path].get(
|
keys["opt"] = self.tiramisu.reflector_objects[self.elt.opt.path].get(
|
||||||
self.calls, self.elt.path
|
self.calls, self.elt.path
|
||||||
)
|
)
|
||||||
|
return
|
||||||
if self.elt.type == "choice":
|
if self.elt.type == "choice":
|
||||||
choices = self.elt.choices
|
keys["values"] = self.populate_calculation(
|
||||||
if isinstance(choices, Calculation):
|
self.elt.choices, return_a_tuple=True
|
||||||
keys["values"] = self.calculation_value(choices)
|
)
|
||||||
else:
|
if self.elt.type == 'regexp':
|
||||||
new_values = []
|
self.object_type = 'Regexp_' + self.option_name
|
||||||
for value in choices:
|
self.tiramisu.text['header'].append(f'''class {self.object_type}(RegexpOption):
|
||||||
if isinstance(value, Calculation):
|
__slots__ = tuple()
|
||||||
new_values.append(self.calculation_value(value))
|
_type = 'value'
|
||||||
elif isinstance(value, str):
|
{self.object_type}._regexp = re_compile(r"{self.elt.regexp}")
|
||||||
new_values.append(self.convert_str(value))
|
''')
|
||||||
else:
|
|
||||||
new_values.append(str(value))
|
|
||||||
keys["values"] = "(" + ", ".join(new_values)
|
|
||||||
if len(new_values) <= 1:
|
|
||||||
keys["values"] += ","
|
|
||||||
keys["values"] += ")"
|
|
||||||
if self.elt.path in self.objectspace.multis:
|
if self.elt.path in self.objectspace.multis:
|
||||||
keys["multi"] = self.objectspace.multis[self.elt.path]
|
keys["multi"] = self.objectspace.multis[self.elt.path]
|
||||||
if hasattr(self.elt, "default") and self.elt.default is not None:
|
if hasattr(self.elt, "default") and self.elt.default is not None:
|
||||||
value = self.elt.default
|
try:
|
||||||
if isinstance(value, str):
|
keys["default"] = self.populate_calculation(self.elt.default)
|
||||||
value = self.convert_str(value)
|
except VariableCalculationDependencyError:
|
||||||
elif isinstance(value, Calculation):
|
pass
|
||||||
value = self.calculation_value(value)
|
|
||||||
elif isinstance(value, list):
|
|
||||||
value = value.copy()
|
|
||||||
for idx, val in enumerate(value):
|
|
||||||
if isinstance(val, Calculation):
|
|
||||||
value[idx] = self.calculation_value(val)
|
|
||||||
else:
|
|
||||||
value[idx] = self.convert_str(val)
|
|
||||||
value = "[" + ", ".join(value) + "]"
|
|
||||||
keys["default"] = value
|
|
||||||
if self.elt.path in self.objectspace.default_multi:
|
if self.elt.path in self.objectspace.default_multi:
|
||||||
value = self.objectspace.default_multi[self.elt.path]
|
try:
|
||||||
|
keys["default_multi"] = self.populate_calculation(
|
||||||
|
self.objectspace.default_multi[self.elt.path]
|
||||||
|
)
|
||||||
|
except VariableCalculationDependencyError:
|
||||||
|
pass
|
||||||
|
if self.elt.validators:
|
||||||
|
keys["validators"] = self.populate_calculation(self.elt.validators)
|
||||||
|
for key, value in (
|
||||||
|
CONVERT_OPTION.get(self.elt.type, {}).get("initkwargs", {}).items()
|
||||||
|
):
|
||||||
if isinstance(value, str):
|
if isinstance(value, str):
|
||||||
value = self.convert_str(value)
|
value = self.convert_str(value)
|
||||||
elif isinstance(value, Calculation):
|
|
||||||
value = self.calculation_value(value)
|
|
||||||
keys["default_multi"] = value
|
|
||||||
if self.elt.validators:
|
|
||||||
validators = []
|
|
||||||
for val in self.elt.validators:
|
|
||||||
if isinstance(val, Calculation):
|
|
||||||
validators.append(self.calculation_value(val))
|
|
||||||
else:
|
|
||||||
validators.append(val)
|
|
||||||
keys["validators"] = "[" + ", ".join(validators) + "]"
|
|
||||||
for key, value in CONVERT_OPTION.get(self.elt.type, {}).get("initkwargs", {}).items():
|
|
||||||
if isinstance(value, str):
|
|
||||||
value = f"'{value}'"
|
|
||||||
keys[key] = value
|
keys[key] = value
|
||||||
if self.elt.params:
|
if self.elt.params:
|
||||||
for param in self.elt.params:
|
for param in self.elt.params:
|
||||||
|
@ -571,12 +529,7 @@ class Family(Common):
|
||||||
keys: list,
|
keys: list,
|
||||||
) -> None:
|
) -> None:
|
||||||
if self.elt.type == "dynamic":
|
if self.elt.type == "dynamic":
|
||||||
dyn = self.tiramisu.reflector_objects[self.elt.variable.path].get(
|
keys["suffixes"] = self.populate_calculation(self.elt.dynamic)
|
||||||
self.calls, self.elt.path
|
|
||||||
)
|
|
||||||
keys[
|
|
||||||
"suffixes"
|
|
||||||
] = f"Calculation(func['calc_value'], Params((ParamOption({dyn}, notraisepropertyerror=True))))"
|
|
||||||
children = []
|
children = []
|
||||||
for path in self.objectspace.parents[self.elt.path]:
|
for path in self.objectspace.parents[self.elt.path]:
|
||||||
children.append(self.objectspace.paths[path])
|
children.append(self.objectspace.paths[path])
|
||||||
|
|
21
src/rougail/update/__init__.py
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
"""
|
||||||
|
Silique (https://www.silique.fr)
|
||||||
|
Copyright (C) 2024
|
||||||
|
|
||||||
|
distribued with GPL-2 or later license
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
"""
|
||||||
|
from .update import RougailUpgrade
|
|
@ -1,4 +1,4 @@
|
||||||
"""Update Rougail XML file to new version
|
"""Update Rougail structure file to new version
|
||||||
|
|
||||||
Cadoles (http://www.cadoles.com)
|
Cadoles (http://www.cadoles.com)
|
||||||
Copyright (C) 2021
|
Copyright (C) 2021
|
||||||
|
@ -23,38 +23,28 @@ along with this program; if not, write to the Free Software
|
||||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import List, Any, Optional, Tuple
|
from os import listdir
|
||||||
from os.path import join, isfile, isdir, basename
|
from os.path import basename, isdir, isfile, join
|
||||||
from os import listdir, makedirs
|
from typing import Any, List, Optional, Tuple
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from lxml.etree import parse, XMLParser, XMLSyntaxError # pylint: disable=E0611
|
from lxml.etree import SubElement # pylint: disable=E0611
|
||||||
from lxml.etree import Element, SubElement, tostring
|
from lxml.etree import Element, XMLParser, XMLSyntaxError, parse, tostring
|
||||||
except ModuleNotFoundError as err:
|
except ModuleNotFoundError as err:
|
||||||
parse = None
|
parse = None
|
||||||
|
|
||||||
# from ast import parse as ast_parse
|
# from ast import parse as ast_parse
|
||||||
from json import dumps
|
from json import dumps
|
||||||
from yaml import safe_load, dump, SafeDumper
|
from ruamel.yaml import YAML
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from .i18n import _
|
from ..config import RougailConfig
|
||||||
from .error import UpgradeError
|
from ..error import UpgradeError
|
||||||
|
from ..i18n import _
|
||||||
|
from ..object_model import CONVERT_OPTION
|
||||||
|
from ..utils import normalize_family
|
||||||
|
|
||||||
from .utils import normalize_family
|
VERSIONS = ["0.10", "1.0", "1.1"]
|
||||||
from .config import RougailConfig
|
|
||||||
from .object_model import CONVERT_OPTION
|
|
||||||
|
|
||||||
|
|
||||||
VERSIONS = ["0.10", "1.0"]
|
|
||||||
|
|
||||||
FIXME_PRINT_FILENAME = True
|
|
||||||
FIXME_PRINT_FILENAME = False
|
|
||||||
FIXME_PRINT_FILE = True
|
|
||||||
FIXME_PRINT_FILE = False
|
|
||||||
FIXME_PRINT_UNKNOWN_VAR = True
|
|
||||||
FIXME_PRINT_UNKNOWN_VAR = False
|
|
||||||
FIXME_PRINT_REMOVE = True
|
|
||||||
FIXME_PRINT_REMOVE = False
|
|
||||||
|
|
||||||
|
|
||||||
def get_function_name(version):
|
def get_function_name(version):
|
||||||
|
@ -65,24 +55,15 @@ def get_function_name(version):
|
||||||
FUNCTION_VERSIONS = [(version, get_function_name(version)) for version in VERSIONS]
|
FUNCTION_VERSIONS = [(version, get_function_name(version)) for version in VERSIONS]
|
||||||
|
|
||||||
|
|
||||||
class NoAliasDumper(SafeDumper):
|
class upgrade_010_to_10:
|
||||||
def ignore_aliases(self, data):
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
class upgrade_010_to_100:
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
dico: dict,
|
dico: dict,
|
||||||
namespace: str,
|
namespace: str,
|
||||||
xmlsrc: str,
|
xmlsrc: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
if FIXME_PRINT_FILE:
|
|
||||||
from pprint import pprint
|
|
||||||
|
|
||||||
pprint(dico)
|
|
||||||
self.xmlsrc = xmlsrc
|
self.xmlsrc = xmlsrc
|
||||||
self.paths = {"family": {}, "variable": {}}
|
self.paths = {"family": {}, "variable": {}, 'dynamic': {}}
|
||||||
self.lists = {
|
self.lists = {
|
||||||
"service": {},
|
"service": {},
|
||||||
"ip": {},
|
"ip": {},
|
||||||
|
@ -90,26 +71,29 @@ class upgrade_010_to_100:
|
||||||
"file": {},
|
"file": {},
|
||||||
}
|
}
|
||||||
self.flatten_paths = {"family": {}, "variable": {}}
|
self.flatten_paths = {"family": {}, "variable": {}}
|
||||||
self.variables = self.parse_variables(dico, namespace)
|
self.variables = self.parse_variables(dico, namespace, namespace, root=True)
|
||||||
self.parse_variables_with_path()
|
self.parse_variables_with_path()
|
||||||
self.parse_services(dico)
|
self.parse_services(dico)
|
||||||
self.parse_constraints(dico)
|
self.parse_constraints(dico)
|
||||||
if FIXME_PRINT_FILE:
|
|
||||||
print("==")
|
|
||||||
pprint(self.variables)
|
|
||||||
pprint(self.services)
|
|
||||||
|
|
||||||
def parse_variables(
|
def parse_variables(
|
||||||
self,
|
self,
|
||||||
family: dict,
|
family: dict,
|
||||||
sub_path: str,
|
sub_path: str,
|
||||||
|
true_sub_path: str,
|
||||||
|
*,
|
||||||
|
root: bool=False,
|
||||||
|
is_dynamic: bool=False,
|
||||||
) -> dict:
|
) -> dict:
|
||||||
new_families = {}
|
new_families = {}
|
||||||
|
if root:
|
||||||
|
new_families['version'] = None
|
||||||
if "variables" in family:
|
if "variables" in family:
|
||||||
for subelt in family["variables"]:
|
for subelt in family["variables"]:
|
||||||
for typ, obj in subelt.items():
|
for typ, obj in subelt.items():
|
||||||
for subobj in obj:
|
for subobj in obj:
|
||||||
getattr(self, f"convert_{typ}")(subobj, new_families, sub_path)
|
local_is_dynamic = is_dynamic or subobj.get('dynamic') is not None
|
||||||
|
getattr(self, f"convert_{typ}")(subobj, new_families, sub_path, true_sub_path, local_is_dynamic)
|
||||||
family.pop("variables")
|
family.pop("variables")
|
||||||
return new_families
|
return new_families
|
||||||
|
|
||||||
|
@ -118,9 +102,17 @@ class upgrade_010_to_100:
|
||||||
family: dict,
|
family: dict,
|
||||||
new_families: dict,
|
new_families: dict,
|
||||||
sub_path: str,
|
sub_path: str,
|
||||||
|
true_sub_path: str,
|
||||||
|
is_dynamic: bool,
|
||||||
) -> None:
|
) -> None:
|
||||||
# name is the key, do not let it in values
|
# name is the key, do not let it in values
|
||||||
name = family.pop("name")
|
name = family.pop("name")
|
||||||
|
if true_sub_path:
|
||||||
|
true_sub_path = true_sub_path + "." + name
|
||||||
|
else:
|
||||||
|
true_sub_path = name
|
||||||
|
if is_dynamic:
|
||||||
|
name += '{{ suffix }}'
|
||||||
if sub_path:
|
if sub_path:
|
||||||
sub_path = sub_path + "." + name
|
sub_path = sub_path + "." + name
|
||||||
else:
|
else:
|
||||||
|
@ -134,7 +126,7 @@ class upgrade_010_to_100:
|
||||||
if typ == "dynamic":
|
if typ == "dynamic":
|
||||||
family["variable"] = self.get_variable_path(value)
|
family["variable"] = self.get_variable_path(value)
|
||||||
# add sub families and sub variables
|
# add sub families and sub variables
|
||||||
sub_families = self.parse_variables(family, sub_path)
|
sub_families = self.parse_variables(family, sub_path, true_sub_path, is_dynamic=is_dynamic)
|
||||||
for sub_name, sub_family in sub_families.copy().items():
|
for sub_name, sub_family in sub_families.copy().items():
|
||||||
if sub_name not in family:
|
if sub_name not in family:
|
||||||
continue
|
continue
|
||||||
|
@ -150,22 +142,34 @@ class upgrade_010_to_100:
|
||||||
variable: dict,
|
variable: dict,
|
||||||
new_families: dict,
|
new_families: dict,
|
||||||
sub_path: str,
|
sub_path: str,
|
||||||
|
true_sub_path: str,
|
||||||
|
is_dynamic: bool,
|
||||||
) -> dict:
|
) -> dict:
|
||||||
name = variable.pop("name")
|
name = variable.pop("name")
|
||||||
|
if is_dynamic:
|
||||||
|
if true_sub_path:
|
||||||
|
true_sub_path = true_sub_path + "." + name
|
||||||
|
else:
|
||||||
|
true_sub_path = name
|
||||||
|
self.flatten_paths["variable"][name] = true_sub_path
|
||||||
|
name += '{{ suffix }}'
|
||||||
if sub_path:
|
if sub_path:
|
||||||
sub_path = sub_path + "." + name
|
sub_path = sub_path + "." + name
|
||||||
else:
|
else:
|
||||||
sub_path = name
|
sub_path = name
|
||||||
|
if is_dynamic:
|
||||||
|
self.paths['dynamic'][true_sub_path] = sub_path
|
||||||
new_families[name] = variable
|
new_families[name] = variable
|
||||||
self.flatten_paths["variable"][name] = sub_path
|
self.flatten_paths["variable"][name] = sub_path
|
||||||
self.paths["variable"][sub_path] = variable
|
self.paths["variable"][sub_path] = variable
|
||||||
if "redefine" not in variable and "value" not in variable and "mandatory" not in variable and ("type" not in variable or variable["type"] != "boolean"):
|
if (
|
||||||
|
"redefine" not in variable
|
||||||
|
and "value" not in variable
|
||||||
|
and "mandatory" not in variable
|
||||||
|
and ("type" not in variable or variable["type"] != "boolean")
|
||||||
|
):
|
||||||
variable["mandatory"] = False
|
variable["mandatory"] = False
|
||||||
if "remove_condition" in variable and variable.pop("remove_condition"):
|
if "remove_condition" in variable and variable.pop("remove_condition"):
|
||||||
if FIXME_PRINT_REMOVE:
|
|
||||||
print(
|
|
||||||
f"variable {name} in file {self.xmlsrc} has remove_condition, all properties (hidden, disabled and mandatory) are set to False"
|
|
||||||
)
|
|
||||||
for prop in ["hidden", "disabled", "mandatory"]:
|
for prop in ["hidden", "disabled", "mandatory"]:
|
||||||
if prop not in variable:
|
if prop not in variable:
|
||||||
variable[prop] = False
|
variable[prop] = False
|
||||||
|
@ -194,24 +198,32 @@ class upgrade_010_to_100:
|
||||||
)(test)
|
)(test)
|
||||||
)
|
)
|
||||||
variable["test"] = tests
|
variable["test"] = tests
|
||||||
|
if variable.get('mandatory', False):
|
||||||
|
del variable["mandatory"]
|
||||||
|
CONVERT_TYPE = {'filename': 'unix_filename',
|
||||||
|
'password': 'secret',
|
||||||
|
}
|
||||||
|
if variable.get('type') in CONVERT_TYPE:
|
||||||
|
variable['type'] = CONVERT_TYPE[variable['type']]
|
||||||
|
|
||||||
def parse_variables_with_path(self):
|
def parse_variables_with_path(self):
|
||||||
for variable in self.paths["variable"].values():
|
for variable in self.paths["variable"].values():
|
||||||
|
multi = variable.get('multi', False)
|
||||||
if "value" in variable:
|
if "value" in variable:
|
||||||
default = variable.pop("value")
|
default = variable.pop("value")
|
||||||
if default is not None:
|
if default is not None:
|
||||||
if not variable.get("multi", False) and len(default) == 1:
|
if not multi and len(default) == 1:
|
||||||
variable["default"] = self.get_value(default[0])
|
variable["default"] = self.get_value(default[0], multi)
|
||||||
else:
|
else:
|
||||||
variable["default"] = [
|
variable["default"] = [
|
||||||
self.get_value(value) for value in default
|
self.get_value(value, multi) for value in default
|
||||||
]
|
]
|
||||||
if "choice" in variable:
|
if "choice" in variable:
|
||||||
if not variable["choice"]:
|
if not variable["choice"]:
|
||||||
variable["choices"] = variable.pop("choice")
|
variable["choices"] = variable.pop("choice")
|
||||||
else:
|
else:
|
||||||
variable["choices"] = [
|
variable["choices"] = [
|
||||||
self.get_value(choice) for choice in variable.pop("choice")
|
self.get_value(choice, multi) for choice in variable.pop("choice")
|
||||||
]
|
]
|
||||||
|
|
||||||
def parse_services(
|
def parse_services(
|
||||||
|
@ -327,34 +339,43 @@ class upgrade_010_to_100:
|
||||||
apply_on_fallback = False
|
apply_on_fallback = False
|
||||||
source = self.get_variable_path(condition["source"])
|
source = self.get_variable_path(condition["source"])
|
||||||
if not source:
|
if not source:
|
||||||
source = f'__{condition["source"]}'
|
source = condition["source"]
|
||||||
name = condition.pop("name")
|
name = condition.pop("name")
|
||||||
prop = name.split("_", 1)[0]
|
prop = name.split("_", 1)[0]
|
||||||
if apply_on_fallback:
|
multi = False
|
||||||
condition_value = True
|
for target in condition["target"]:
|
||||||
else:
|
typ = target.get("type", "variable")
|
||||||
condition_value = self.params_condition_to_jinja(
|
if typ == "variable":
|
||||||
source, condition["param"], name.endswith("if_in")
|
variable_path = self.get_variable_path(target["text"])
|
||||||
)
|
if variable_path is None:
|
||||||
|
continue
|
||||||
|
variable = self.paths["variable"][variable_path]
|
||||||
|
if variable.get('multi', False):
|
||||||
|
multi = True
|
||||||
|
if apply_on_fallback:
|
||||||
|
condition_value = True
|
||||||
|
else:
|
||||||
|
if "{{ suffix }}" in source:
|
||||||
|
force_param = {'__var': source}
|
||||||
|
source = '__var'
|
||||||
|
else:
|
||||||
|
force_param = None
|
||||||
|
condition_value = self.params_condition_to_jinja(
|
||||||
|
prop, source, condition["param"], name.endswith("if_in"), multi
|
||||||
|
)
|
||||||
|
if force_param:
|
||||||
|
condition_value.setdefault('params', {}).update(force_param)
|
||||||
for target in condition["target"]:
|
for target in condition["target"]:
|
||||||
typ = target.get("type", "variable")
|
typ = target.get("type", "variable")
|
||||||
if typ == "variable":
|
if typ == "variable":
|
||||||
variable_path = self.get_variable_path(target["text"])
|
variable_path = self.get_variable_path(target["text"])
|
||||||
if variable_path is None:
|
if variable_path is None:
|
||||||
if FIXME_PRINT_UNKNOWN_VAR and not target.get("optional", False):
|
|
||||||
print(
|
|
||||||
f'pffff la target {target["text"]} de la condition n\'est pas trouvable'
|
|
||||||
)
|
|
||||||
continue
|
continue
|
||||||
variable = self.paths["variable"][variable_path]
|
variable = self.paths["variable"][variable_path]
|
||||||
variable[prop] = condition_value
|
variable[prop] = condition_value
|
||||||
elif typ == "family":
|
elif typ == "family":
|
||||||
family_path = self.get_family_path(target["text"])
|
family_path = self.get_family_path(target["text"])
|
||||||
if family_path is None:
|
if family_path is None:
|
||||||
if FIXME_PRINT_UNKNOWN_VAR and not target.get("optional", False):
|
|
||||||
print(
|
|
||||||
f'pffff la target {target["text"]} de la condition n\'est pas trouvable'
|
|
||||||
)
|
|
||||||
continue
|
continue
|
||||||
family = self.paths["family"][family_path]
|
family = self.paths["family"][family_path]
|
||||||
family[prop] = condition_value
|
family[prop] = condition_value
|
||||||
|
@ -386,10 +407,6 @@ class upgrade_010_to_100:
|
||||||
for target in check["target"]:
|
for target in check["target"]:
|
||||||
variable_path = self.get_variable_path(target["text"])
|
variable_path = self.get_variable_path(target["text"])
|
||||||
if variable_path is None:
|
if variable_path is None:
|
||||||
if FIXME_PRINT_UNKNOWN_VAR and not target.get("optional", False):
|
|
||||||
print(
|
|
||||||
f'pffff la target {target["text"]} dans le check n\'est pas trouvable'
|
|
||||||
)
|
|
||||||
continue
|
continue
|
||||||
variable = self.paths["variable"][variable_path]
|
variable = self.paths["variable"][variable_path]
|
||||||
if "validators" in variable and variable["validators"] is None:
|
if "validators" in variable and variable["validators"] is None:
|
||||||
|
@ -400,7 +417,7 @@ class upgrade_010_to_100:
|
||||||
check["param"] = [
|
check["param"] = [
|
||||||
{"text": variable_path, "type": "variable"}
|
{"text": variable_path, "type": "variable"}
|
||||||
] + check.get("param", [])
|
] + check.get("param", [])
|
||||||
check_value = self.convert_param_function(check)
|
check_value = self.convert_param_function(check, variable.get('multi', False))
|
||||||
variable.setdefault("validators", []).append(check_value)
|
variable.setdefault("validators", []).append(check_value)
|
||||||
|
|
||||||
def parse_fill(
|
def parse_fill(
|
||||||
|
@ -410,27 +427,30 @@ class upgrade_010_to_100:
|
||||||
for target in fill.pop("target"):
|
for target in fill.pop("target"):
|
||||||
params = []
|
params = []
|
||||||
variable_path = self.get_variable_path(target["text"])
|
variable_path = self.get_variable_path(target["text"])
|
||||||
if variable_path is None:
|
if variable_path in self.paths["dynamic"]:
|
||||||
if FIXME_PRINT_UNKNOWN_VAR and not target.get("optional", False):
|
variable_path = self.paths["dynamic"][variable_path]
|
||||||
print(
|
if variable_path in self.paths["variable"]:
|
||||||
f'pffff la target {target["text"]} dans le fill n\'est pas trouvable'
|
variable = self.paths["variable"][variable_path]
|
||||||
)
|
if fill.get("type") == "jinja":
|
||||||
continue
|
fill_value = {
|
||||||
variable = self.paths["variable"][variable_path]
|
"type": "jinja",
|
||||||
if fill.get("type") == "jinja":
|
"jinja": fill["name"],
|
||||||
fill_value = {
|
}
|
||||||
"type": "jinja",
|
else:
|
||||||
"jinja": fill["name"],
|
fill_value = self.convert_param_function(fill, variable.get('multi', False))
|
||||||
}
|
variable["default"] = fill_value
|
||||||
|
if variable.get('mandatory') is False:
|
||||||
|
del variable['mandatory']
|
||||||
else:
|
else:
|
||||||
fill_value = self.convert_param_function(fill)
|
raise Exception(f'cannot set fill to unknown variable "{variable_path}"')
|
||||||
variable["default"] = fill_value
|
|
||||||
|
|
||||||
def params_condition_to_jinja(
|
def params_condition_to_jinja(
|
||||||
self,
|
self,
|
||||||
|
prop: str,
|
||||||
path: str,
|
path: str,
|
||||||
params: List[dict],
|
params: List[dict],
|
||||||
if_in: bool,
|
if_in: bool,
|
||||||
|
multi: bool,
|
||||||
) -> str:
|
) -> str:
|
||||||
new_params = {}
|
new_params = {}
|
||||||
jinja = "{% if "
|
jinja = "{% if "
|
||||||
|
@ -438,15 +458,15 @@ class upgrade_010_to_100:
|
||||||
if idx:
|
if idx:
|
||||||
jinja += " or "
|
jinja += " or "
|
||||||
|
|
||||||
new_param, value = self.get_jinja_param_and_value(param)
|
new_param, value = self.get_jinja_param_and_value(param, multi)
|
||||||
if value:
|
if value:
|
||||||
jinja += path + " == " + value
|
jinja += path + " == " + value
|
||||||
if new_param:
|
if new_param:
|
||||||
new_params |= new_param
|
new_params |= new_param
|
||||||
if if_in:
|
if if_in:
|
||||||
jinja += " %}true{% else %}false{% endif %}"
|
jinja += " %}" + prop + "{% endif %}"
|
||||||
else:
|
else:
|
||||||
jinja += " %}false{% else %}true{% endif %}"
|
jinja += " %}{% else %}" + prop + "{% endif %}"
|
||||||
ret = {
|
ret = {
|
||||||
"type": "jinja",
|
"type": "jinja",
|
||||||
"jinja": jinja,
|
"jinja": jinja,
|
||||||
|
@ -458,12 +478,11 @@ class upgrade_010_to_100:
|
||||||
def get_value(
|
def get_value(
|
||||||
self,
|
self,
|
||||||
param: dict,
|
param: dict,
|
||||||
|
multi: bool,
|
||||||
) -> Any:
|
) -> Any:
|
||||||
# <!ATTLIST type (string|number|nil|space|boolean|variable|function|information|suffix|index) "string">
|
|
||||||
typ = param.get("type", "string")
|
typ = param.get("type", "string")
|
||||||
if typ == "string":
|
if typ == "string":
|
||||||
value = param["text"]
|
value = param.get("text")
|
||||||
# value = dumps(value, ensure_ascii=False)
|
|
||||||
elif typ == "number":
|
elif typ == "number":
|
||||||
value = int(param["text"])
|
value = int(param["text"])
|
||||||
elif typ == "nil":
|
elif typ == "nil":
|
||||||
|
@ -485,7 +504,7 @@ class upgrade_010_to_100:
|
||||||
if "propertyerror" in param:
|
if "propertyerror" in param:
|
||||||
value["propertyerror"] = param["propertyerror"]
|
value["propertyerror"] = param["propertyerror"]
|
||||||
elif typ == "function":
|
elif typ == "function":
|
||||||
value = self.convert_param_function(param)
|
value = self.convert_param_function(param, multi)
|
||||||
elif typ == "information":
|
elif typ == "information":
|
||||||
value = {
|
value = {
|
||||||
"type": "information",
|
"type": "information",
|
||||||
|
@ -503,10 +522,11 @@ class upgrade_010_to_100:
|
||||||
def get_jinja_param_and_value(
|
def get_jinja_param_and_value(
|
||||||
self,
|
self,
|
||||||
param,
|
param,
|
||||||
|
multi: bool,
|
||||||
) -> Tuple[list, Any]:
|
) -> Tuple[list, Any]:
|
||||||
new_param = None
|
new_param = None
|
||||||
typ = param.get("type", "string")
|
typ = param.get("type", "string")
|
||||||
value = self.get_value(param)
|
value = self.get_value(param, multi)
|
||||||
if isinstance(value, dict):
|
if isinstance(value, dict):
|
||||||
if typ == "information":
|
if typ == "information":
|
||||||
key = normalize_family(value["information"])
|
key = normalize_family(value["information"])
|
||||||
|
@ -514,17 +534,25 @@ class upgrade_010_to_100:
|
||||||
attr_name = f'{value["variable"]}.{key}'
|
attr_name = f'{value["variable"]}.{key}'
|
||||||
else:
|
else:
|
||||||
attr_name = key
|
attr_name = key
|
||||||
attr_name = f"__information.{attr_name}"
|
attr_name = f"__information_{attr_name}"
|
||||||
new_param = {attr_name: value}
|
new_param = {attr_name: value}
|
||||||
value = attr_name
|
value = attr_name
|
||||||
elif typ in ["index", "suffix"]:
|
elif typ in ["index", "suffix"]:
|
||||||
attr_name = f"__{typ}"
|
if 'name' in value:
|
||||||
new_param = {attr_name: value}
|
attr_name = value['name']
|
||||||
|
else:
|
||||||
|
attr_name = f"__{typ}"
|
||||||
|
new_param = {attr_name: {"type": typ}}
|
||||||
value = attr_name
|
value = attr_name
|
||||||
elif "propertyerror" in param or "optional" in param:
|
elif "propertyerror" in param or "optional" in param:
|
||||||
attr_name = value["variable"]
|
attr_name = value["variable"]
|
||||||
new_param = {attr_name: value}
|
new_param = {attr_name: value}
|
||||||
value = value[typ]
|
value = value[typ]
|
||||||
|
elif "{{ suffix }}" in value[typ]:
|
||||||
|
path = value[typ]
|
||||||
|
attr_name = path.split('.')[-1][:-12] # remove {{ suffix }}
|
||||||
|
new_param = {attr_name: value}
|
||||||
|
value = attr_name
|
||||||
else:
|
else:
|
||||||
value = value[typ]
|
value = value[typ]
|
||||||
if not value:
|
if not value:
|
||||||
|
@ -536,23 +564,35 @@ class upgrade_010_to_100:
|
||||||
def convert_param_function(
|
def convert_param_function(
|
||||||
self,
|
self,
|
||||||
param: dict,
|
param: dict,
|
||||||
|
multi: bool,
|
||||||
) -> str:
|
) -> str:
|
||||||
text = param["name"]
|
text = param["name"]
|
||||||
params = {}
|
params = {}
|
||||||
|
# multi = False
|
||||||
if "param" in param and param["param"]:
|
if "param" in param and param["param"]:
|
||||||
|
if text == 'calc_value' and len(param["param"]) == 1 and isinstance(param["param"][0], dict) and param["param"][0].get('type') == 'variable' and param["param"][0].get("text"):
|
||||||
|
value = param["param"][0]["text"]
|
||||||
|
path = self.get_variable_path(value)
|
||||||
|
if not path:
|
||||||
|
path = value
|
||||||
|
ret = {"type": "variable", "variable": path}
|
||||||
|
if 'optional' in param["param"][0]:
|
||||||
|
ret['optional'] = param["param"][0]["optional"]
|
||||||
|
return ret
|
||||||
first, *others = param["param"]
|
first, *others = param["param"]
|
||||||
new_param, first = self.get_jinja_param_and_value(first)
|
new_param, first = self.get_jinja_param_and_value(first, multi)
|
||||||
text = f"{first} | {text}"
|
text = f"{first} | {text}"
|
||||||
if new_param:
|
if new_param:
|
||||||
params |= new_param
|
params |= new_param
|
||||||
if others:
|
if others:
|
||||||
values = []
|
values = []
|
||||||
for param in others:
|
for param in others:
|
||||||
new_param, value = self.get_jinja_param_and_value(param)
|
new_param, value = self.get_jinja_param_and_value(param, multi)
|
||||||
if new_param:
|
if new_param:
|
||||||
params |= new_param
|
params |= new_param
|
||||||
# if param.get('type') != 'variable' or value is not None:
|
|
||||||
if "name" in param:
|
if "name" in param:
|
||||||
|
if param["name"] == "multi" and value == "true":
|
||||||
|
multi = True
|
||||||
values.append(f'{param["name"]}={value}')
|
values.append(f'{param["name"]}={value}')
|
||||||
else:
|
else:
|
||||||
values.append(value)
|
values.append(value)
|
||||||
|
@ -561,7 +601,12 @@ class upgrade_010_to_100:
|
||||||
text += ")"
|
text += ")"
|
||||||
else:
|
else:
|
||||||
text += "()"
|
text += "()"
|
||||||
text = "{{ " + text + " }}"
|
if not multi:
|
||||||
|
text = "{{ " + text + " }}"
|
||||||
|
else:
|
||||||
|
text = """{% for __variable in """ + text + """ %}
|
||||||
|
{{ __variable }}
|
||||||
|
{% endfor %}"""
|
||||||
ret = {"type": "jinja", "jinja": text}
|
ret = {"type": "jinja", "jinja": text}
|
||||||
if params:
|
if params:
|
||||||
ret["params"] = params
|
ret["params"] = params
|
||||||
|
@ -576,10 +621,10 @@ class upgrade_010_to_100:
|
||||||
and path in self.flatten_paths["variable"]
|
and path in self.flatten_paths["variable"]
|
||||||
):
|
):
|
||||||
path = self.flatten_paths["variable"][path]
|
path = self.flatten_paths["variable"][path]
|
||||||
|
if path in self.paths["dynamic"]:
|
||||||
|
path = self.paths["dynamic"][path]
|
||||||
if path not in self.paths["variable"]:
|
if path not in self.paths["variable"]:
|
||||||
if FIXME_PRINT_UNKNOWN_VAR:
|
return path
|
||||||
print("pffff impossible de trouver la variable", path)
|
|
||||||
return
|
|
||||||
return path
|
return path
|
||||||
|
|
||||||
def get_family_path(
|
def get_family_path(
|
||||||
|
@ -589,8 +634,6 @@ class upgrade_010_to_100:
|
||||||
if path not in self.paths["family"] and path in self.flatten_paths["family"]:
|
if path not in self.paths["family"] and path in self.flatten_paths["family"]:
|
||||||
path = self.flatten_paths["family"][path]
|
path = self.flatten_paths["family"][path]
|
||||||
if path not in self.paths["family"]:
|
if path not in self.paths["family"]:
|
||||||
if FIXME_PRINT_UNKNOWN_VAR:
|
|
||||||
print("pffff impossible de trouver la famille", path)
|
|
||||||
return
|
return
|
||||||
return path
|
return path
|
||||||
|
|
||||||
|
@ -613,42 +656,38 @@ class RougailUpgrade:
|
||||||
rougailconfig = RougailConfig
|
rougailconfig = RougailConfig
|
||||||
self.rougailconfig = rougailconfig
|
self.rougailconfig = rougailconfig
|
||||||
|
|
||||||
def load_dictionaries(
|
def run(
|
||||||
self,
|
self,
|
||||||
# srcfolder: str,
|
|
||||||
dstfolder: str,
|
|
||||||
services_dstfolder: Optional[str],
|
|
||||||
extra_dstfolder: Optional[str] = None,
|
|
||||||
# namespace: str,
|
|
||||||
# display: bool=True,
|
|
||||||
):
|
):
|
||||||
if extra_dstfolder is None:
|
for dict_dir, dest_dir in zip(self.rougailconfig["main_dictionaries"], self.rougailconfig["upgrade_options.main_dictionaries"]):
|
||||||
extra_dstfolder = dstfolder
|
self._load_dictionaries(
|
||||||
self._load_dictionaries(
|
dict_dir,
|
||||||
self.rougailconfig["dictionaries_dir"],
|
dest_dir,
|
||||||
dstfolder,
|
normalize_family(self.rougailconfig["main_namespace"]),
|
||||||
services_dstfolder,
|
)
|
||||||
self.rougailconfig["variable_namespace"],
|
if self.rougailconfig['main_namespace']:
|
||||||
)
|
if self.rougailconfig["extra_dictionaries"]:
|
||||||
for namespace, extra_dirs in self.rougailconfig["extra_dictionaries"].items():
|
dst_extra_dir = self.rougailconfig["upgrade_options.extra_dictionary"]
|
||||||
extra_dstsubfolder = join(extra_dstfolder, namespace)
|
for namespace, extra_dirs in self.rougailconfig["extra_dictionaries"].items():
|
||||||
if not isdir(extra_dstsubfolder):
|
extra_dstsubfolder = Path(dst_extra_dir) / namespace
|
||||||
makedirs(extra_dstsubfolder)
|
if not extra_dstsubfolder.is_dir():
|
||||||
for extra_dir in extra_dirs:
|
extra_dstsubfolder.mkdir()
|
||||||
self._load_dictionaries(
|
for extra_dir in extra_dirs:
|
||||||
extra_dir,
|
self._load_dictionaries(
|
||||||
extra_dstsubfolder,
|
str(extra_dir),
|
||||||
None,
|
str(extra_dstsubfolder),
|
||||||
namespace,
|
normalize_family(namespace),
|
||||||
)
|
)
|
||||||
|
|
||||||
def _load_dictionaries(
|
def _load_dictionaries(
|
||||||
self,
|
self,
|
||||||
srcfolder: str,
|
srcfolder: str,
|
||||||
dstfolder: str,
|
dstfolder: Optional[str],
|
||||||
services_dstfolder: Optional[str],
|
|
||||||
namespace: str,
|
namespace: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
if dstfolder is None:
|
||||||
|
dstfolder = srcfolder
|
||||||
|
Path(dstfolder).mkdir(parents=True, exist_ok=True)
|
||||||
filenames = [
|
filenames = [
|
||||||
filename
|
filename
|
||||||
for filename in listdir(srcfolder)
|
for filename in listdir(srcfolder)
|
||||||
|
@ -657,21 +696,11 @@ class RougailUpgrade:
|
||||||
filenames.sort()
|
filenames.sort()
|
||||||
for filename in filenames:
|
for filename in filenames:
|
||||||
xmlsrc = Path(srcfolder) / Path(filename)
|
xmlsrc = Path(srcfolder) / Path(filename)
|
||||||
ymlfile = filename[:-3] + "yml"
|
|
||||||
xmldst = Path(dstfolder) / Path(ymlfile)
|
ymldst = Path(dstfolder) / (Path(filename).stem + '.yml')
|
||||||
if xmldst.is_file():
|
|
||||||
raise Exception(
|
|
||||||
f'cannot update "{xmlsrc}" destination file "{xmldst}" already exists'
|
|
||||||
)
|
|
||||||
if services_dstfolder:
|
|
||||||
ymldst_services = Path(services_dstfolder) / ymlfile
|
|
||||||
if ymldst_services.is_file():
|
|
||||||
raise Exception(
|
|
||||||
f'cannot update "{xmlsrc}" destination file "{ymldst_services}" already exists'
|
|
||||||
)
|
|
||||||
if filename.endswith(".xml"):
|
if filename.endswith(".xml"):
|
||||||
if parse is None:
|
if parse is None:
|
||||||
raise Exception('XML module is not installed')
|
raise Exception("XML module is not installed")
|
||||||
try:
|
try:
|
||||||
parser = XMLParser(remove_blank_text=True)
|
parser = XMLParser(remove_blank_text=True)
|
||||||
document = parse(xmlsrc, parser)
|
document = parse(xmlsrc, parser)
|
||||||
|
@ -683,61 +712,43 @@ class RougailUpgrade:
|
||||||
)
|
)
|
||||||
ext = "xml"
|
ext = "xml"
|
||||||
else:
|
else:
|
||||||
with xmlsrc.open() as xml_fh:
|
with xmlsrc.open() as file_fh:
|
||||||
root = safe_load(xml_fh)
|
root = YAML(typ="safe").load(file_fh)
|
||||||
search_function_name = get_function_name(root["version"])
|
search_function_name = get_function_name(str(root["version"]))
|
||||||
ext = "yml"
|
ext = "yml"
|
||||||
function_found = False
|
function_found = False
|
||||||
if FIXME_PRINT_FILENAME:
|
root_services = None
|
||||||
print(
|
|
||||||
"========================================================================"
|
|
||||||
)
|
|
||||||
print(xmlsrc)
|
|
||||||
print(
|
|
||||||
"========================================================================"
|
|
||||||
)
|
|
||||||
for version, function_version in FUNCTION_VERSIONS:
|
for version, function_version in FUNCTION_VERSIONS:
|
||||||
if function_found and hasattr(self, function_version):
|
if function_found and hasattr(self, function_version):
|
||||||
# if display:
|
|
||||||
# print(f' - convert {filename} to version {version}')
|
|
||||||
upgrade_help = self.upgrade_help.get(function_version, {}).get(
|
upgrade_help = self.upgrade_help.get(function_version, {}).get(
|
||||||
filename, {}
|
filename, {}
|
||||||
)
|
)
|
||||||
if upgrade_help.get("remove") is True:
|
if upgrade_help.get("remove") is True:
|
||||||
continue
|
continue
|
||||||
root, root_services, new_type = getattr(self, function_version)(
|
root, root_services_, new_type = getattr(self, function_version)(
|
||||||
root, upgrade_help, namespace, xmlsrc, ext
|
root, upgrade_help, namespace, xmlsrc, ext
|
||||||
)
|
)
|
||||||
|
if root_services_ is not None:
|
||||||
|
root_services = root_services_
|
||||||
if function_version == search_function_name:
|
if function_version == search_function_name:
|
||||||
function_found = True
|
function_found = True
|
||||||
if root:
|
if root != {'version': None}:
|
||||||
root["version"] = version
|
root["version"] = float(version)
|
||||||
xmldst.parent.mkdir(parents=True, exist_ok=True)
|
with ymldst.open("w") as ymlfh:
|
||||||
with xmldst.open("w") as ymlfh:
|
yaml = YAML()
|
||||||
dump(
|
yaml.dump(
|
||||||
root,
|
root,
|
||||||
ymlfh,
|
ymlfh,
|
||||||
allow_unicode=True,
|
|
||||||
sort_keys=False,
|
|
||||||
Dumper=NoAliasDumper,
|
|
||||||
)
|
)
|
||||||
if root_services and services_dstfolder:
|
# if root_services and services_dstfolder:
|
||||||
root_services["version"] = version
|
# root_services["version"] = version
|
||||||
ymldst_services.parent.mkdir(parents=True, exist_ok=True)
|
# ymldst_services.parent.mkdir(parents=True, exist_ok=True)
|
||||||
with ymldst_services.open("w") as ymlfh:
|
# with ymldst_services.open("w") as ymlfh:
|
||||||
dump(
|
# yaml = YAML()
|
||||||
root_services,
|
# yaml.dump(
|
||||||
ymlfh,
|
# root_services,
|
||||||
allow_unicode=True,
|
# ymlfh,
|
||||||
sort_keys=False,
|
# )
|
||||||
Dumper=NoAliasDumper,
|
|
||||||
)
|
|
||||||
|
|
||||||
# if not self.dtd.validate(document):
|
|
||||||
# dtd_error = self.dtd.error_log.filter_from_errors()[0]
|
|
||||||
# msg = _(f'not a valid XML file: {dtd_error}')
|
|
||||||
# raise DictConsistencyError(msg, 43, [xmlfile])
|
|
||||||
# yield xmlfile, document.getroot()
|
|
||||||
|
|
||||||
def _attribut_to_bool(self, variable):
|
def _attribut_to_bool(self, variable):
|
||||||
for prop in [
|
for prop in [
|
||||||
|
@ -766,10 +777,11 @@ class RougailUpgrade:
|
||||||
def _attribut_to_int(self, variable):
|
def _attribut_to_int(self, variable):
|
||||||
for prop in ["mode"]:
|
for prop in ["mode"]:
|
||||||
if prop in variable:
|
if prop in variable:
|
||||||
if variable[prop] in ['expert', 'normal']:
|
if variable[prop] in ["expert", "normal"]:
|
||||||
variable[prop] = {'expert': 'advanced',
|
variable[prop] = {
|
||||||
'normal': 'standard',
|
"expert": "advanced",
|
||||||
}.get(variable[prop])
|
"normal": "standard",
|
||||||
|
}.get(variable[prop])
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
variable[prop] = int(variable[prop])
|
variable[prop] = int(variable[prop])
|
||||||
|
@ -891,6 +903,60 @@ class RougailUpgrade:
|
||||||
dico = {obj_name: dico}
|
dico = {obj_name: dico}
|
||||||
return dico
|
return dico
|
||||||
|
|
||||||
|
def _update_1_1(self, root):
|
||||||
|
new_root = {}
|
||||||
|
update_root = False
|
||||||
|
for key, value in root.items():
|
||||||
|
new_root[key] = value
|
||||||
|
if not isinstance(value, dict):
|
||||||
|
continue
|
||||||
|
# migrate dynamic family
|
||||||
|
if (
|
||||||
|
("variable" in value and isinstance(value["variable"], str))
|
||||||
|
or ("_variable" in value and isinstance(value["_variable"], str))
|
||||||
|
) and (
|
||||||
|
("_type" in value and value["_type"] == "dynamic")
|
||||||
|
or ("type" in value and value["type"] == "dynamic")
|
||||||
|
):
|
||||||
|
value["dynamic"] = {
|
||||||
|
"type": "variable",
|
||||||
|
"variable": value.pop("variable"),
|
||||||
|
"propertyerror": False,
|
||||||
|
}
|
||||||
|
if '{{ suffix }}' not in key:
|
||||||
|
new_root[key + '{{ suffix }}'] = new_root.pop(key)
|
||||||
|
update_root = True
|
||||||
|
self._update_1_1(value)
|
||||||
|
for typ, obj in {'boolean': bool,
|
||||||
|
'number': int,
|
||||||
|
'string': str,
|
||||||
|
'float': float,
|
||||||
|
}.items():
|
||||||
|
if value.get('type') == typ:
|
||||||
|
default = value.get('default')
|
||||||
|
if default is None or default == []:
|
||||||
|
continue
|
||||||
|
if isinstance(default, obj):
|
||||||
|
del value['type']
|
||||||
|
elif isinstance(default, list) and isinstance(default[0], obj):
|
||||||
|
del value['type']
|
||||||
|
if value.get('multi') and isinstance(value.get('default'), list):
|
||||||
|
del value['multi']
|
||||||
|
if update_root:
|
||||||
|
root.clear()
|
||||||
|
root.update(new_root)
|
||||||
|
|
||||||
|
def update_1_1(
|
||||||
|
self,
|
||||||
|
root,
|
||||||
|
upgrade_help: dict,
|
||||||
|
namespace: str,
|
||||||
|
xmlsrc: str,
|
||||||
|
ext: str,
|
||||||
|
):
|
||||||
|
self._update_1_1(root)
|
||||||
|
return root, None, "yml"
|
||||||
|
|
||||||
def update_1_0(
|
def update_1_0(
|
||||||
self,
|
self,
|
||||||
root: "Element",
|
root: "Element",
|
||||||
|
@ -906,22 +972,12 @@ class RougailUpgrade:
|
||||||
objects = root.find(typ)
|
objects = root.find(typ)
|
||||||
if objects is None:
|
if objects is None:
|
||||||
objects = []
|
objects = []
|
||||||
new_objects = self._xml_to_yaml(objects, typ, variables, "")
|
new_objects = self._xml_to_yaml(objects, typ, variables, namespace)
|
||||||
if new_objects[typ]:
|
if new_objects[typ]:
|
||||||
new_root.update(new_objects)
|
new_root.update(new_objects)
|
||||||
# services = root.find('services')
|
|
||||||
# if services is None:
|
|
||||||
# services = []
|
|
||||||
# new_services = self._xml_to_yaml_service(services)
|
|
||||||
# if new_services:
|
|
||||||
# new_root['services'] = new_services
|
|
||||||
# paths = self._get_path_variables(variables,
|
|
||||||
# namespace == 'configuration',
|
|
||||||
# namespace,
|
|
||||||
# )
|
|
||||||
else:
|
else:
|
||||||
new_root = root
|
new_root = root
|
||||||
variables, services = upgrade_010_to_100(new_root, namespace, xmlsrc).get()
|
variables, services = upgrade_010_to_10(new_root, namespace, xmlsrc).get()
|
||||||
return variables, services, "yml"
|
return variables, services, "yml"
|
||||||
|
|
||||||
def update_0_10(
|
def update_0_10(
|
||||||
|
@ -966,7 +1022,6 @@ class RougailUpgrade:
|
||||||
if not has_value:
|
if not has_value:
|
||||||
value = SubElement(variable, "value")
|
value = SubElement(variable, "value")
|
||||||
value.text = choices[0]
|
value.text = choices[0]
|
||||||
variable.attrib["mandatory"] = "True"
|
|
||||||
|
|
||||||
# convert group to leadership
|
# convert group to leadership
|
||||||
groups = []
|
groups = []
|
|
@ -30,6 +30,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
from typing import List, Union
|
from typing import List, Union
|
||||||
from unicodedata import normalize, combining
|
from unicodedata import normalize, combining
|
||||||
import re
|
import re
|
||||||
|
from itertools import chain
|
||||||
|
|
||||||
from importlib.machinery import SourceFileLoader
|
from importlib.machinery import SourceFileLoader
|
||||||
from importlib.util import spec_from_loader, module_from_spec
|
from importlib.util import spec_from_loader, module_from_spec
|
||||||
|
@ -37,7 +38,9 @@ from importlib.util import spec_from_loader, module_from_spec
|
||||||
from jinja2 import DictLoader, TemplateSyntaxError
|
from jinja2 import DictLoader, TemplateSyntaxError
|
||||||
from jinja2.sandbox import SandboxedEnvironment
|
from jinja2.sandbox import SandboxedEnvironment
|
||||||
from jinja2.parser import Parser
|
from jinja2.parser import Parser
|
||||||
from jinja2.nodes import Getattr
|
from jinja2.nodes import Name, Getattr
|
||||||
|
|
||||||
|
from tiramisu.config import get_common_path
|
||||||
|
|
||||||
from .i18n import _
|
from .i18n import _
|
||||||
from .error import DictConsistencyError
|
from .error import DictConsistencyError
|
||||||
|
@ -62,15 +65,16 @@ def normalize_family(family_name: str) -> str:
|
||||||
"""replace space, accent, uppercase, ... by valid character"""
|
"""replace space, accent, uppercase, ... by valid character"""
|
||||||
if not family_name:
|
if not family_name:
|
||||||
return
|
return
|
||||||
|
family_name = family_name.lower()
|
||||||
family_name = family_name.replace("-", "_").replace(" ", "_").replace(".", "_")
|
family_name = family_name.replace("-", "_").replace(" ", "_").replace(".", "_")
|
||||||
nfkd_form = normalize("NFKD", family_name)
|
nfkd_form = normalize("NFKD", family_name)
|
||||||
family_name = "".join([c for c in nfkd_form if not combining(c)])
|
family_name = "".join([c for c in nfkd_form if not combining(c)])
|
||||||
return family_name.lower()
|
return family_name.lower()
|
||||||
|
|
||||||
|
|
||||||
def load_modules(eosfunc_file) -> List[str]:
|
def load_modules(name, module) -> List[str]:
|
||||||
"""list all functions in eosfunc"""
|
"""list all functions in a module"""
|
||||||
loader = SourceFileLoader("eosfunc", eosfunc_file)
|
loader = SourceFileLoader(name, module)
|
||||||
spec = spec_from_loader(loader.name, loader)
|
spec = spec_from_loader(loader.name, loader)
|
||||||
eosfunc = module_from_spec(spec)
|
eosfunc = module_from_spec(spec)
|
||||||
loader.exec_module(eosfunc)
|
loader.exec_module(eosfunc)
|
||||||
|
@ -87,11 +91,14 @@ def get_realpath(
|
||||||
|
|
||||||
|
|
||||||
def get_jinja_variable_to_param(
|
def get_jinja_variable_to_param(
|
||||||
|
current_path: str,
|
||||||
jinja_text,
|
jinja_text,
|
||||||
objectspace,
|
objectspace,
|
||||||
xmlfiles,
|
xmlfiles,
|
||||||
functions,
|
functions,
|
||||||
path_prefix,
|
path_prefix,
|
||||||
|
version,
|
||||||
|
namespace,
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
env = SandboxedEnvironment(loader=DictLoader({"tmpl": jinja_text}))
|
env = SandboxedEnvironment(loader=DictLoader({"tmpl": jinja_text}))
|
||||||
|
@ -104,16 +111,58 @@ def get_jinja_variable_to_param(
|
||||||
return g.node.name + "." + g.attr
|
return g.node.name + "." + g.attr
|
||||||
|
|
||||||
variables = set()
|
variables = set()
|
||||||
|
if objectspace.namespace is None:
|
||||||
|
for n in parsed_content.find_all(Name):
|
||||||
|
variables.add(n.name)
|
||||||
for g in parsed_content.find_all(Getattr):
|
for g in parsed_content.find_all(Getattr):
|
||||||
variables.add(recurse_getattr(g))
|
variables.add(recurse_getattr(g))
|
||||||
except TemplateSyntaxError as err:
|
except TemplateSyntaxError as err:
|
||||||
msg = _(f'error in jinja "{jinja_text}": {err}')
|
msg = _(f'error in jinja "{jinja_text}" for the variable "{ current_path }": {err}')
|
||||||
raise Exception(msg) from err
|
raise DictConsistencyError(msg, 39, xmlfiles) from err
|
||||||
variables = list(variables)
|
variables = list(variables)
|
||||||
variables.sort()
|
variables.sort(reverse=True)
|
||||||
|
founded_variables = {}
|
||||||
|
unknown_variables = []
|
||||||
for variable_path in variables:
|
for variable_path in variables:
|
||||||
variable, suffix, dynamic = objectspace.paths.get_with_dynamic(
|
variable, suffix = objectspace.paths.get_with_dynamic(
|
||||||
get_realpath(variable_path, path_prefix)
|
variable_path,
|
||||||
|
path_prefix,
|
||||||
|
current_path,
|
||||||
|
version,
|
||||||
|
namespace,
|
||||||
|
xmlfiles,
|
||||||
)
|
)
|
||||||
if variable and variable.path in objectspace.variables:
|
if variable and variable.path in objectspace.variables:
|
||||||
yield variable, suffix, variable_path, dynamic
|
founded_variables[variable_path] = (suffix, variable)
|
||||||
|
else:
|
||||||
|
sub_family = variable_path + '.'
|
||||||
|
for founded_variable in chain(founded_variables, unknown_variables):
|
||||||
|
if founded_variable.startswith(sub_family):
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
unknown_variables.append(variable_path)
|
||||||
|
|
||||||
|
for variable_path in unknown_variables:
|
||||||
|
for v in founded_variables:
|
||||||
|
if get_common_path(v, variable_path) == v:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
root_path = None
|
||||||
|
vpath = variable_path
|
||||||
|
while '.' in vpath:
|
||||||
|
vpath = vpath.rsplit('.', 1)[0]
|
||||||
|
variable, suffix = objectspace.paths.get_with_dynamic(
|
||||||
|
vpath,
|
||||||
|
path_prefix,
|
||||||
|
current_path,
|
||||||
|
version,
|
||||||
|
namespace,
|
||||||
|
xmlfiles,
|
||||||
|
)
|
||||||
|
if variable and variable.path in objectspace.families:
|
||||||
|
root_path = vpath
|
||||||
|
break
|
||||||
|
if root_path:
|
||||||
|
yield {}, None, root_path
|
||||||
|
for variable_path, data in founded_variables.items():
|
||||||
|
yield data[1], data[0], variable_path
|
||||||
|
|
5
tests/data/dict1/dict.yml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
# dict with a correct version declared
|
||||||
|
#version: "1.1"
|
||||||
|
hello:
|
||||||
|
type: string
|
||||||
|
default: world
|
5
tests/data/dict2/dict.yml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
# dict with a correct version declared
|
||||||
|
version: "1.0"
|
||||||
|
hello:
|
||||||
|
type: string
|
||||||
|
default: world
|
9
tests/dictionaries/00_0empty/tiramisu/base.py
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
from tiramisu import *
|
||||||
|
from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
|
||||||
|
from re import compile as re_compile
|
||||||
|
from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription
|
||||||
|
load_functions('tests/dictionaries/../eosfunc/test.py')
|
||||||
|
ALLOWED_LEADER_PROPERTIES.add("basic")
|
||||||
|
ALLOWED_LEADER_PROPERTIES.add("standard")
|
||||||
|
ALLOWED_LEADER_PROPERTIES.add("advanced")
|
||||||
|
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[])
|
11
tests/dictionaries/00_0empty/tiramisu/multi.py
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
from tiramisu import *
|
||||||
|
from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
|
||||||
|
from re import compile as re_compile
|
||||||
|
from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription
|
||||||
|
load_functions('tests/dictionaries/../eosfunc/test.py')
|
||||||
|
ALLOWED_LEADER_PROPERTIES.add("basic")
|
||||||
|
ALLOWED_LEADER_PROPERTIES.add("standard")
|
||||||
|
ALLOWED_LEADER_PROPERTIES.add("advanced")
|
||||||
|
optiondescription_1 = OptionDescription(name="1", doc="1", children=[], properties=frozenset({"advanced"}))
|
||||||
|
optiondescription_2 = OptionDescription(name="2", doc="2", children=[], properties=frozenset({"advanced"}))
|
||||||
|
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[optiondescription_1, optiondescription_2])
|
9
tests/dictionaries/00_0empty/tiramisu/no_namespace.py
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
from tiramisu import *
|
||||||
|
from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
|
||||||
|
from re import compile as re_compile
|
||||||
|
from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription
|
||||||
|
load_functions('tests/dictionaries/../eosfunc/test.py')
|
||||||
|
ALLOWED_LEADER_PROPERTIES.add("basic")
|
||||||
|
ALLOWED_LEADER_PROPERTIES.add("standard")
|
||||||
|
ALLOWED_LEADER_PROPERTIES.add("advanced")
|
||||||
|
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[])
|
|
@ -0,0 +1,3 @@
|
||||||
|
---
|
||||||
|
_version: '1.1'
|
||||||
|
version: # a variable
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"rougail.version": {
|
||||||
|
"owner": "default",
|
||||||
|
"value": null
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"rougail.version": null
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"rougail.version": {
|
||||||
|
"owner": "default",
|
||||||
|
"value": null
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
["rougail.version"]
|
11
tests/dictionaries/00_0version_underscore/tiramisu/base.py
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
from tiramisu import *
|
||||||
|
from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
|
||||||
|
from re import compile as re_compile
|
||||||
|
from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription
|
||||||
|
load_functions('tests/dictionaries/../eosfunc/test.py')
|
||||||
|
ALLOWED_LEADER_PROPERTIES.add("basic")
|
||||||
|
ALLOWED_LEADER_PROPERTIES.add("standard")
|
||||||
|
ALLOWED_LEADER_PROPERTIES.add("advanced")
|
||||||
|
option_2 = StrOption(name="version", doc="a variable", properties=frozenset({"basic", "mandatory"}), informations={'type': 'string'})
|
||||||
|
optiondescription_1 = OptionDescription(name="rougail", doc="Rougail", children=[option_2], properties=frozenset({"basic"}))
|
||||||
|
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[optiondescription_1])
|
15
tests/dictionaries/00_0version_underscore/tiramisu/multi.py
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
from tiramisu import *
|
||||||
|
from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
|
||||||
|
from re import compile as re_compile
|
||||||
|
from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription
|
||||||
|
load_functions('tests/dictionaries/../eosfunc/test.py')
|
||||||
|
ALLOWED_LEADER_PROPERTIES.add("basic")
|
||||||
|
ALLOWED_LEADER_PROPERTIES.add("standard")
|
||||||
|
ALLOWED_LEADER_PROPERTIES.add("advanced")
|
||||||
|
option_3 = StrOption(name="version", doc="a variable", properties=frozenset({"basic", "mandatory"}), informations={'type': 'string'})
|
||||||
|
optiondescription_2 = OptionDescription(name="rougail", doc="Rougail", children=[option_3], properties=frozenset({"basic"}))
|
||||||
|
optiondescription_1 = OptionDescription(name="1", doc="1", children=[optiondescription_2], properties=frozenset({"basic"}))
|
||||||
|
option_6 = StrOption(name="version", doc="a variable", properties=frozenset({"basic", "mandatory"}), informations={'type': 'string'})
|
||||||
|
optiondescription_5 = OptionDescription(name="rougail", doc="Rougail", children=[option_6], properties=frozenset({"basic"}))
|
||||||
|
optiondescription_4 = OptionDescription(name="2", doc="2", children=[optiondescription_5], properties=frozenset({"basic"}))
|
||||||
|
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[optiondescription_1, optiondescription_4])
|
|
@ -0,0 +1,10 @@
|
||||||
|
from tiramisu import *
|
||||||
|
from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
|
||||||
|
from re import compile as re_compile
|
||||||
|
from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription
|
||||||
|
load_functions('tests/dictionaries/../eosfunc/test.py')
|
||||||
|
ALLOWED_LEADER_PROPERTIES.add("basic")
|
||||||
|
ALLOWED_LEADER_PROPERTIES.add("standard")
|
||||||
|
ALLOWED_LEADER_PROPERTIES.add("advanced")
|
||||||
|
option_1 = StrOption(name="version", doc="a variable", properties=frozenset({"basic", "mandatory"}), informations={'type': 'string'})
|
||||||
|
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[option_1])
|
3
tests/dictionaries/00_1empty_variable/makedict/base.json
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"rougail.empty": null
|
||||||
|
}
|
11
tests/dictionaries/00_1empty_variable/tiramisu/base.py
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
from tiramisu import *
|
||||||
|
from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
|
||||||
|
from re import compile as re_compile
|
||||||
|
from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription
|
||||||
|
load_functions('tests/dictionaries/../eosfunc/test.py')
|
||||||
|
ALLOWED_LEADER_PROPERTIES.add("basic")
|
||||||
|
ALLOWED_LEADER_PROPERTIES.add("standard")
|
||||||
|
ALLOWED_LEADER_PROPERTIES.add("advanced")
|
||||||
|
option_2 = StrOption(name="empty", doc="empty", properties=frozenset({"basic", "mandatory"}), informations={'type': 'string'})
|
||||||
|
optiondescription_1 = OptionDescription(name="rougail", doc="Rougail", children=[option_2], properties=frozenset({"basic"}))
|
||||||
|
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[optiondescription_1])
|
15
tests/dictionaries/00_1empty_variable/tiramisu/multi.py
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
from tiramisu import *
|
||||||
|
from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
|
||||||
|
from re import compile as re_compile
|
||||||
|
from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription
|
||||||
|
load_functions('tests/dictionaries/../eosfunc/test.py')
|
||||||
|
ALLOWED_LEADER_PROPERTIES.add("basic")
|
||||||
|
ALLOWED_LEADER_PROPERTIES.add("standard")
|
||||||
|
ALLOWED_LEADER_PROPERTIES.add("advanced")
|
||||||
|
option_3 = StrOption(name="empty", doc="empty", properties=frozenset({"basic", "mandatory"}), informations={'type': 'string'})
|
||||||
|
optiondescription_2 = OptionDescription(name="rougail", doc="Rougail", children=[option_3], properties=frozenset({"basic"}))
|
||||||
|
optiondescription_1 = OptionDescription(name="1", doc="1", children=[optiondescription_2], properties=frozenset({"basic"}))
|
||||||
|
option_6 = StrOption(name="empty", doc="empty", properties=frozenset({"basic", "mandatory"}), informations={'type': 'string'})
|
||||||
|
optiondescription_5 = OptionDescription(name="rougail", doc="Rougail", children=[option_6], properties=frozenset({"basic"}))
|
||||||
|
optiondescription_4 = OptionDescription(name="2", doc="2", children=[optiondescription_5], properties=frozenset({"basic"}))
|
||||||
|
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[optiondescription_1, optiondescription_4])
|
|
@ -0,0 +1,10 @@
|
||||||
|
from tiramisu import *
|
||||||
|
from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
|
||||||
|
from re import compile as re_compile
|
||||||
|
from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription
|
||||||
|
load_functions('tests/dictionaries/../eosfunc/test.py')
|
||||||
|
ALLOWED_LEADER_PROPERTIES.add("basic")
|
||||||
|
ALLOWED_LEADER_PROPERTIES.add("standard")
|
||||||
|
ALLOWED_LEADER_PROPERTIES.add("advanced")
|
||||||
|
option_1 = StrOption(name="empty", doc="empty", properties=frozenset({"basic", "mandatory"}), informations={'type': 'string'})
|
||||||
|
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[option_1])
|
|
@ -0,0 +1,10 @@
|
||||||
|
---
|
||||||
|
version: 1.1
|
||||||
|
var1: "no" # a first variable
|
||||||
|
var2:
|
||||||
|
description: a second variable
|
||||||
|
multi: true
|
||||||
|
default:
|
||||||
|
jinja: |
|
||||||
|
{{ _.var1 }}
|
||||||
|
description: the value of var1
|
13
tests/dictionaries/00_2default_calculated/tiramisu/base.py
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
from tiramisu import *
|
||||||
|
from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
|
||||||
|
from re import compile as re_compile
|
||||||
|
from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription
|
||||||
|
load_functions('tests/dictionaries/../eosfunc/test.py')
|
||||||
|
ALLOWED_LEADER_PROPERTIES.add("basic")
|
||||||
|
ALLOWED_LEADER_PROPERTIES.add("standard")
|
||||||
|
ALLOWED_LEADER_PROPERTIES.add("advanced")
|
||||||
|
dict_env['default_rougail.var2'] = "{{ _.var1 }}\n"
|
||||||
|
option_2 = StrOption(name="var1", doc="a first variable", default="no", properties=frozenset({"mandatory", "standard"}), informations={'type': 'string'})
|
||||||
|
option_3 = StrOption(name="var2", doc="a second variable", multi=True, default=Calculation(func['jinja_to_function'], Params((), kwargs={'__internal_jinja': ParamValue("default_rougail.var2"), '__internal_type': ParamValue("string"), '__internal_multi': ParamValue(True), '__internal_files': ParamValue(['tests/dictionaries/00_2default_calculated/dictionaries/rougail/00-base.yml']), '__internal_attribute': ParamValue("default"), '__internal_variable': ParamValue("rougail.var2"), '_.var1': ParamOption(option_2)})), properties=frozenset({"mandatory", "standard"}), informations={'type': 'string'})
|
||||||
|
optiondescription_1 = OptionDescription(name="rougail", doc="Rougail", children=[option_2, option_3], properties=frozenset({"standard"}))
|
||||||
|
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[optiondescription_1])
|
19
tests/dictionaries/00_2default_calculated/tiramisu/multi.py
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
from tiramisu import *
|
||||||
|
from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
|
||||||
|
from re import compile as re_compile
|
||||||
|
from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription
|
||||||
|
load_functions('tests/dictionaries/../eosfunc/test.py')
|
||||||
|
ALLOWED_LEADER_PROPERTIES.add("basic")
|
||||||
|
ALLOWED_LEADER_PROPERTIES.add("standard")
|
||||||
|
ALLOWED_LEADER_PROPERTIES.add("advanced")
|
||||||
|
dict_env['default_1.rougail.var2'] = "{{ _.var1 }}\n"
|
||||||
|
dict_env['default_2.rougail.var2'] = "{{ _.var1 }}\n"
|
||||||
|
option_3 = StrOption(name="var1", doc="a first variable", default="no", properties=frozenset({"mandatory", "standard"}), informations={'type': 'string'})
|
||||||
|
option_4 = StrOption(name="var2", doc="a second variable", multi=True, default=Calculation(func['jinja_to_function'], Params((), kwargs={'__internal_jinja': ParamValue("default_1.rougail.var2"), '__internal_type': ParamValue("string"), '__internal_multi': ParamValue(True), '__internal_files': ParamValue(['tests/dictionaries/00_2default_calculated/dictionaries/rougail/00-base.yml']), '__internal_attribute': ParamValue("default"), '__internal_variable': ParamValue("1.rougail.var2"), '_.var1': ParamOption(option_3)})), properties=frozenset({"mandatory", "standard"}), informations={'type': 'string'})
|
||||||
|
optiondescription_2 = OptionDescription(name="rougail", doc="Rougail", children=[option_3, option_4], properties=frozenset({"standard"}))
|
||||||
|
optiondescription_1 = OptionDescription(name="1", doc="1", children=[optiondescription_2], properties=frozenset({"standard"}))
|
||||||
|
option_7 = StrOption(name="var1", doc="a first variable", default="no", properties=frozenset({"mandatory", "standard"}), informations={'type': 'string'})
|
||||||
|
option_8 = StrOption(name="var2", doc="a second variable", multi=True, default=Calculation(func['jinja_to_function'], Params((), kwargs={'__internal_jinja': ParamValue("default_2.rougail.var2"), '__internal_type': ParamValue("string"), '__internal_multi': ParamValue(True), '__internal_files': ParamValue(['tests/dictionaries/00_2default_calculated/dictionaries/rougail/00-base.yml']), '__internal_attribute': ParamValue("default"), '__internal_variable': ParamValue("2.rougail.var2"), '_.var1': ParamOption(option_7)})), properties=frozenset({"mandatory", "standard"}), informations={'type': 'string'})
|
||||||
|
optiondescription_6 = OptionDescription(name="rougail", doc="Rougail", children=[option_7, option_8], properties=frozenset({"standard"}))
|
||||||
|
optiondescription_5 = OptionDescription(name="2", doc="2", children=[optiondescription_6], properties=frozenset({"standard"}))
|
||||||
|
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[optiondescription_1, optiondescription_5])
|
|
@ -0,0 +1,12 @@
|
||||||
|
from tiramisu import *
|
||||||
|
from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
|
||||||
|
from re import compile as re_compile
|
||||||
|
from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription
|
||||||
|
load_functions('tests/dictionaries/../eosfunc/test.py')
|
||||||
|
ALLOWED_LEADER_PROPERTIES.add("basic")
|
||||||
|
ALLOWED_LEADER_PROPERTIES.add("standard")
|
||||||
|
ALLOWED_LEADER_PROPERTIES.add("advanced")
|
||||||
|
dict_env['default_var2'] = "{{ _.var1 }}\n"
|
||||||
|
option_1 = StrOption(name="var1", doc="a first variable", default="no", properties=frozenset({"mandatory", "standard"}), informations={'type': 'string'})
|
||||||
|
option_2 = StrOption(name="var2", doc="a second variable", multi=True, default=Calculation(func['jinja_to_function'], Params((), kwargs={'__internal_jinja': ParamValue("default_var2"), '__internal_type': ParamValue("string"), '__internal_multi': ParamValue(True), '__internal_files': ParamValue(['tests/dictionaries/00_2default_calculated/dictionaries/rougail/00-base.yml']), '__internal_attribute': ParamValue("default"), '__internal_variable': ParamValue("var2"), '_.var1': ParamOption(option_1)})), properties=frozenset({"mandatory", "standard"}), informations={'type': 'string'})
|
||||||
|
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[option_1, option_2])
|
|
@ -0,0 +1,15 @@
|
||||||
|
---
|
||||||
|
version: 1.1
|
||||||
|
var1: # a first variable
|
||||||
|
- 'no'
|
||||||
|
- 'yes'
|
||||||
|
- maybe
|
||||||
|
var2:
|
||||||
|
description: a second variable
|
||||||
|
multi: true
|
||||||
|
default:
|
||||||
|
jinja: |
|
||||||
|
{% for val in _.var1 %}
|
||||||
|
{{ val }}
|
||||||
|
{% endfor %}
|
||||||
|
description: the value of _.var1
|
|
@ -0,0 +1,13 @@
|
||||||
|
from tiramisu import *
|
||||||
|
from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
|
||||||
|
from re import compile as re_compile
|
||||||
|
from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription
|
||||||
|
load_functions('tests/dictionaries/../eosfunc/test.py')
|
||||||
|
ALLOWED_LEADER_PROPERTIES.add("basic")
|
||||||
|
ALLOWED_LEADER_PROPERTIES.add("standard")
|
||||||
|
ALLOWED_LEADER_PROPERTIES.add("advanced")
|
||||||
|
dict_env['default_rougail.var2'] = "{% for val in _.var1 %}\n{{ val }}\n{% endfor %}\n"
|
||||||
|
option_2 = StrOption(name="var1", doc="a first variable", multi=True, default=["no", "yes", "maybe"], default_multi="no", properties=frozenset({"mandatory", "standard"}), informations={'type': 'string'})
|
||||||
|
option_3 = StrOption(name="var2", doc="a second variable", multi=True, default=Calculation(func['jinja_to_function'], Params((), kwargs={'__internal_jinja': ParamValue("default_rougail.var2"), '__internal_type': ParamValue("string"), '__internal_multi': ParamValue(True), '__internal_files': ParamValue(['tests/dictionaries/00_2default_calculated_multi/dictionaries/rougail/00-base.yml']), '__internal_attribute': ParamValue("default"), '__internal_variable': ParamValue("rougail.var2"), '_.var1': ParamOption(option_2)})), properties=frozenset({"mandatory", "standard"}), informations={'type': 'string'})
|
||||||
|
optiondescription_1 = OptionDescription(name="rougail", doc="Rougail", children=[option_2, option_3], properties=frozenset({"standard"}))
|
||||||
|
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[optiondescription_1])
|
|
@ -0,0 +1,19 @@
|
||||||
|
from tiramisu import *
|
||||||
|
from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
|
||||||
|
from re import compile as re_compile
|
||||||
|
from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription
|
||||||
|
load_functions('tests/dictionaries/../eosfunc/test.py')
|
||||||
|
ALLOWED_LEADER_PROPERTIES.add("basic")
|
||||||
|
ALLOWED_LEADER_PROPERTIES.add("standard")
|
||||||
|
ALLOWED_LEADER_PROPERTIES.add("advanced")
|
||||||
|
dict_env['default_1.rougail.var2'] = "{% for val in _.var1 %}\n{{ val }}\n{% endfor %}\n"
|
||||||
|
dict_env['default_2.rougail.var2'] = "{% for val in _.var1 %}\n{{ val }}\n{% endfor %}\n"
|
||||||
|
option_3 = StrOption(name="var1", doc="a first variable", multi=True, default=["no", "yes", "maybe"], default_multi="no", properties=frozenset({"mandatory", "standard"}), informations={'type': 'string'})
|
||||||
|
option_4 = StrOption(name="var2", doc="a second variable", multi=True, default=Calculation(func['jinja_to_function'], Params((), kwargs={'__internal_jinja': ParamValue("default_1.rougail.var2"), '__internal_type': ParamValue("string"), '__internal_multi': ParamValue(True), '__internal_files': ParamValue(['tests/dictionaries/00_2default_calculated_multi/dictionaries/rougail/00-base.yml']), '__internal_attribute': ParamValue("default"), '__internal_variable': ParamValue("1.rougail.var2"), '_.var1': ParamOption(option_3)})), properties=frozenset({"mandatory", "standard"}), informations={'type': 'string'})
|
||||||
|
optiondescription_2 = OptionDescription(name="rougail", doc="Rougail", children=[option_3, option_4], properties=frozenset({"standard"}))
|
||||||
|
optiondescription_1 = OptionDescription(name="1", doc="1", children=[optiondescription_2], properties=frozenset({"standard"}))
|
||||||
|
option_7 = StrOption(name="var1", doc="a first variable", multi=True, default=["no", "yes", "maybe"], default_multi="no", properties=frozenset({"mandatory", "standard"}), informations={'type': 'string'})
|
||||||
|
option_8 = StrOption(name="var2", doc="a second variable", multi=True, default=Calculation(func['jinja_to_function'], Params((), kwargs={'__internal_jinja': ParamValue("default_2.rougail.var2"), '__internal_type': ParamValue("string"), '__internal_multi': ParamValue(True), '__internal_files': ParamValue(['tests/dictionaries/00_2default_calculated_multi/dictionaries/rougail/00-base.yml']), '__internal_attribute': ParamValue("default"), '__internal_variable': ParamValue("2.rougail.var2"), '_.var1': ParamOption(option_7)})), properties=frozenset({"mandatory", "standard"}), informations={'type': 'string'})
|
||||||
|
optiondescription_6 = OptionDescription(name="rougail", doc="Rougail", children=[option_7, option_8], properties=frozenset({"standard"}))
|
||||||
|
optiondescription_5 = OptionDescription(name="2", doc="2", children=[optiondescription_6], properties=frozenset({"standard"}))
|
||||||
|
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[optiondescription_1, optiondescription_5])
|
|
@ -0,0 +1,12 @@
|
||||||
|
from tiramisu import *
|
||||||
|
from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
|
||||||
|
from re import compile as re_compile
|
||||||
|
from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription
|
||||||
|
load_functions('tests/dictionaries/../eosfunc/test.py')
|
||||||
|
ALLOWED_LEADER_PROPERTIES.add("basic")
|
||||||
|
ALLOWED_LEADER_PROPERTIES.add("standard")
|
||||||
|
ALLOWED_LEADER_PROPERTIES.add("advanced")
|
||||||
|
dict_env['default_var2'] = "{% for val in _.var1 %}\n{{ val }}\n{% endfor %}\n"
|
||||||
|
option_1 = StrOption(name="var1", doc="a first variable", multi=True, default=["no", "yes", "maybe"], default_multi="no", properties=frozenset({"mandatory", "standard"}), informations={'type': 'string'})
|
||||||
|
option_2 = StrOption(name="var2", doc="a second variable", multi=True, default=Calculation(func['jinja_to_function'], Params((), kwargs={'__internal_jinja': ParamValue("default_var2"), '__internal_type': ParamValue("string"), '__internal_multi': ParamValue(True), '__internal_files': ParamValue(['tests/dictionaries/00_2default_calculated_multi/dictionaries/rougail/00-base.yml']), '__internal_attribute': ParamValue("default"), '__internal_variable': ParamValue("var2"), '_.var1': ParamOption(option_1)})), properties=frozenset({"mandatory", "standard"}), informations={'type': 'string'})
|
||||||
|
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[option_1, option_2])
|