Compare commits

..

202 commits

Author SHA1 Message Date
gwen
887f5794af ok up to dynfam 2026-01-28 16:15:02 +01:00
gwen
c368334d05 corrections 2026-01-28 11:50:37 +01:00
gwen
4f87d9d5b9 all commentaries ok 2026-01-26 09:51:38 +01:00
gwen
096925eba7 modifs dynfam 2026-01-26 09:36:25 +01:00
gwen
9a78088cc6 dynamically built fam 2026-01-23 16:54:36 +01:00
gwen
ed09bfd4ae update dynfam 2026-01-22 18:50:29 +01:00
gwen
10eee79dc2 dynfam 2026-01-22 17:47:24 +01:00
gwen
c08a1fca3a rename 2026-01-21 11:30:33 +01:00
08c6a9e027 fix: requirements.txt 2026-01-21 11:27:37 +01:00
gwen
26a9a7edf9 remarques ok 2026-01-20 12:12:44 +01:00
gwen
9d0f0e9ae9 15 ok 2026-01-20 11:55:47 +01:00
gwen
1e6331f477 commentaires jusqu'au 15 2026-01-19 22:05:27 +01:00
gwen
a346e23af6 calculated comments ok 2026-01-19 14:33:11 +01:00
gwen
9c9c313681 cli.unknown_user_data_error dans la sortie 2026-01-10 09:18:14 +01:00
gwen
2cf3b0e752 update 2 2026-01-10 09:16:00 +01:00
gwen
095f0be622 update 2026-01-10 08:45:20 +01:00
gwen
ff9c0650fc calculated ok 2026-01-09 13:01:49 +01:00
gwen
3823642352 https domainname 2026-01-09 11:27:00 +01:00
gwen
7faf7c4310 disabled/hidden ok 2026-01-08 16:31:33 +01:00
gwen
b1f0e7e3de hidden 2026-01-08 15:47:23 +01:00
gwen
c0a6ef2223 calculated default value ok 2026-01-08 11:48:54 +01:00
gwen
44d848b943 add calculated 040 2026-01-08 10:44:35 +01:00
gwen
6da067f206 typos 2026-01-07 16:42:21 +01:00
gwen
eeba802047 disabled ok 2026-01-06 11:48:15 +01:00
gwen
9f2299ee3e disabled / hidden 2026-01-06 10:41:35 +01:00
gwen
0008d03772 disabled 2026-01-05 13:59:56 +01:00
gwen
d47405c61d disabled family 2025-12-15 08:42:43 +01:00
8b02c79dcc review 2025-12-11 21:45:56 +01:00
gwen
aabc937d1c last review 2025-12-10 08:22:18 +01:00
gwen
fe4e98624a last review 2025-12-10 08:07:34 +01:00
gwen
c850c11e98 derniere relecture 2025-12-10 06:35:17 +01:00
gwen
01c4325b86 2025-12-05 review 2025-12-05 20:30:16 +01:00
gwen
01d99cbd01 domain name ok 2025-12-04 21:27:36 +01:00
gwen
0a10dfbd7d domain name 2025-12-04 20:36:51 +01:00
gwen
b437572de9 choice 2025-12-04 19:30:01 +01:00
gwen
921fc0f481 family -> reviewed 2025-12-04 18:26:08 +01:00
gwen
a9b6a00087 choice.rst -> reviewed 2025-12-04 18:02:32 +01:00
gwen
d9ac902740 preliminary -> reviewed 2025-12-04 18:00:09 +01:00
gwen
b22c1817b9 git switch --detach 2025-12-04 14:21:12 +01:00
gwen
8e2fcd073d debut 040 2025-12-04 14:20:18 +01:00
gwen
9413831037 domainname page ok 2025-12-03 22:12:08 +01:00
gwen
9d5ca18bdc cleaning 2025-12-03 21:13:38 +01:00
gwen
8bc7c9b04d domainname and port ok 2025-12-03 18:58:41 +01:00
gwen
40df27bf90 domain name ok 2025-12-03 15:04:23 +01:00
gwen
65d4440694 push 2025-12-02 11:52:21 +01:00
gwen
be4983e524 domain name type 2025-12-02 11:08:54 +01:00
gwen
920780384d family OK 2025-12-01 21:35:27 +01:00
gwen
e61ff106eb family user value 2025-12-01 20:03:52 +01:00
gwen
cb9b534669 configuration and user data file in preliminary 2025-12-01 18:16:24 +01:00
gwen
50bf8e696d family 2025-11-30 18:22:56 +01:00
gwen
76e59b22dd prerequisites 2025-11-30 10:02:03 +01:00
gwen
4057ef6fc7 prerequisites 2025-11-30 09:41:08 +01:00
gwen
f1af25d11a choice ok 2025-11-27 19:15:02 +01:00
gwen
9ca4f31986 preliminary + choice 2025-11-27 18:26:09 +01:00
gwen
b5c919dc8d familiy 2025-11-27 16:59:21 +01:00
gwen
1364b3b4f2 typo 2025-11-27 11:49:07 +01:00
gwen
2e99a30e38 choice 2025-11-27 11:30:55 +01:00
1cd89747de fix: 2025-11-25 07:14:09 +01:00
gwen
22c7472297 preliminary : fist page ok 2025-11-24 16:00:54 +01:00
gwen
778a8f5e48 add image 2025-11-24 13:54:16 +01:00
gwen
7980e9f034 rewrite of the choice type 2025-11-22 13:42:28 +01:00
gwen
dbe78f2334 remote terminal CLI command 2025-11-22 12:57:17 +01:00
gwen
7fc56b2429 separation of the choice 2025-11-22 12:24:54 +01:00
gwen
4da1408c7f typos and wrong urls 2025-11-22 11:27:24 +01:00
gwen
99394348b5 wrong url links 2025-11-22 11:11:45 +01:00
gwen
cb52c774be add modif 2025-11-22 08:08:14 +01:00
gwen
2b0dd5e5bd add extinclude warning in directive 2025-11-22 08:04:51 +01:00
9c2885ce94 fix: update versions 2025-11-21 08:36:39 +01:00
d3bfa99ee4 fix: hidden/disabled 2025-11-19 20:29:56 +01:00
543d33a29b fix: controle acces 2025-11-19 17:41:22 +01:00
6c210f3c33 fix: types 2025-11-19 15:57:55 +01:00
1adae63e18 fix: caracteristique.md 2025-11-19 12:31:29 +01:00
gwen
9d6778ce4f unexisting file (need to raise a warning with extinclude) 2025-11-19 07:57:01 +01:00
gwen
f0018fcf93 adding titles corresponding to the git repo tutorial 2025-11-19 07:52:37 +01:00
gwen
043038526d add links tutorial 2025-11-13 16:22:23 +01:00
gwen
20b90f7c70 update requirements.txt 2025-11-08 10:32:32 +01:00
gwen
9d67d93107 installation 2025-11-08 09:50:20 +01:00
gwen
947ae2a029 moving the type definition in the variable's page instead of the tutorial 2025-11-08 09:45:56 +01:00
gwen
55213e18db third party libraries 2025-11-07 22:02:28 +01:00
gwen
638442273b add download links 2025-11-07 17:26:23 +01:00
gwen
a0e556e7c1 added git repo links 2025-11-07 09:05:44 +01:00
aca97a3e92 update link 2025-11-07 06:45:46 +01:00
bc0de749c7 feat: add multi parameters 2025-11-06 21:54:48 +01:00
gwen
9961d1c958 renaming structure files 2025-11-06 11:49:14 +01:00
gwen
8912e6dd63 getting started removal 2025-11-06 11:46:31 +01:00
gwen
f3c3fe4df0 typo 2025-11-05 17:34:12 +01:00
gwen
c378fbbf89 typos 2025-11-05 17:00:19 +01:00
gwen
82b934db02 begin meld getting started and tutorial 2025-11-05 16:38:44 +01:00
gwen
7f4efef0be add link to the rougail-tuto repo 2025-11-05 15:16:45 +01:00
gwen
e511f07932 calculation typos 2025-11-05 10:18:35 +01:00
gwen
e64a89990d diff for dynamic family 2025-11-05 09:47:42 +01:00
gwen
5cc1e50669 typos 2025-11-05 09:38:36 +01:00
gwen
7a20515114 user datas 2025-11-05 08:59:46 +01:00
5b1356970f modifs 2025-11-05 08:30:00 +01:00
bc415deba9 modifs 2025-11-05 06:54:03 +01:00
gwen
6a87c23e2b doc(typo): YAML sphinx coding standards 2025-10-29 17:18:55 +01:00
gwen
42d5762a5f doc(typo): wrong image relative path 2025-10-27 18:22:40 +01:00
gwen
22a5621c5d relative path 2025-10-27 09:20:33 +01:00
9d856f9de0 feat: user_datas/output 2025-10-27 06:50:10 +01:00
2eeedd70e2 feat: start user_datas/output documentation 2025-10-26 22:21:21 +01:00
4bb50361d5 feat: tags 2025-10-26 14:31:48 +01:00
360c97c581 feat: number => integer in documentation 2025-10-26 13:31:47 +01:00
1277cce9d9 feat: library documentation 2025-10-26 13:29:29 +01:00
96975461d0 fix: update rougailconfig parameters 2025-10-26 09:35:55 +01:00
gwen
961cee372e add image 2025-09-29 16:59:25 +02:00
gwen
16af68e9c8 add pages 2025-09-29 16:58:12 +02:00
gwen
6505ac9ab9 jinja calculation 2025-09-19 16:30:07 +02:00
gwen
6b6aeceb6e calcuation and dynamic family 2025-09-18 17:17:02 +02:00
gwen
91112a905a jinja and calculations 2025-09-16 18:51:35 +02:00
gwen
1da10b7970 dynfam and identifier 2025-09-03 15:35:37 +02:00
gwen
15bd2c4986 add dynfam image 2025-07-28 14:50:01 +02:00
gwen
8a7959ed11 dynamic family 2025-07-28 14:49:01 +02:00
gwen
ad80093520 struct file in image 2025-06-26 18:31:01 +02:00
gwen
7e163e56f0 proofreading 2025-06-26 17:53:37 +02:00
gwen
f7701917d0 proofreader 2025-06-26 17:17:06 +02:00
gwen
a3d7258a64 proofreading 2025-06-26 12:54:33 +02:00
gwen
c2e8e3fd5a typos 2025-05-31 14:42:14 +02:00
gwen
8d7dde7bcc boolean type and hidden property 2025-05-31 14:14:19 +02:00
gwen
ad2c9cf2ce calculated default value 2025-05-27 15:57:30 +02:00
gwen
0293d4d455 contexte 2025-05-27 15:33:32 +02:00
gwen
dd0ae7d03b hidden property 2025-05-27 11:03:29 +02:00
gwen
e49f731aa2 disabled/hidden 2025-05-26 22:09:29 +02:00
gwen
284a176a0a boolean var 2025-05-14 13:17:08 +02:00
gwen
edde6e2a85 diabled property 2025-05-14 10:59:17 +02:00
gwen
3aed61cdb1 disabled 2025-05-06 16:49:03 +02:00
gwen
4af0e7cbe2 disabled 2025-05-05 21:39:54 +02:00
gwen
935b33d28e modifs 2025-05-05 21:39:32 +02:00
gwen
91ffd19336 typos 2025-02-21 20:26:08 +01:00
gwen
fce54f7c84 disable 2025-02-21 17:31:26 +01:00
gwen
cfb6017f32 proxymode 2025-02-21 17:14:30 +01:00
gwen
a932d7e1c3 proxymode 2025-02-21 07:23:52 +01:00
gwen
2da85eb0e8 preliminary and proxy mode ok 2025-02-20 14:32:11 +01:00
gwen
9ca27d7f51 proxy mode tutorial 2025-02-20 14:23:34 +01:00
gwen
2d39fe8bad getting started update 2025-02-20 11:35:55 +01:00
gwen
10a25b0cfe dictionary -> structure file 2025-02-09 11:20:36 +01:00
gwen
0f0d955e9f dictionary removed, replaced by structure file 2025-02-09 10:47:48 +01:00
gwen
038cd9429a add distant url for the default value 2025-02-09 10:36:19 +01:00
gwen
cc54cf675f preliminary file in the tutorial ok 2025-02-05 07:18:50 +01:00
gwen
b4efbf1d71 phrases 2025-02-04 22:01:19 +01:00
gwen
28fe83cfb2 css 2025-02-04 21:52:54 +01:00
gwen
6edda03d79 output box css 2025-02-04 21:35:42 +01:00
gwen
55bda5cd8c default value in the tutorial 2025-02-04 16:38:07 +01:00
gwen
543d54144b getting started -> hello world to tutorial 2025-02-04 15:48:49 +01:00
gwen
98f788ee9b modifs 2025-02-03 20:20:15 +01:00
gwen
bf6120f95e confvar 2024-10-28 21:21:56 +01:00
gwen
04f30963f4 family in turorial 2024-10-28 21:08:22 +01:00
gwen
eda0632809 missign rougail-tutorial tags 2024-10-28 15:29:11 +01:00
gwen
c2111fe581 typo 2024-10-21 19:58:35 +02:00
gwen
9870d949b9 typo 2024-10-21 19:56:41 +02:00
gwen
5dc7d2b630 structure files 2024-10-21 19:50:26 +02:00
gwen
fbc0744f61 keypoints 2024-10-21 18:28:34 +02:00
gwen
d7b2e9b521 roles 2024-10-21 15:57:03 +02:00
gwen
5cf4fb16a4 typo 2024-10-21 15:52:28 +02:00
gwen
a0546a137f remove rougail code (suite) 2024-10-21 15:47:41 +02:00
gwen
b1ed2d3aaf remove rougail code 2024-10-21 15:42:52 +02:00
gwen
39f01b36a1 library at the end 2024-10-21 15:25:00 +02:00
gwen
c361629b63 update slides and def 2024-10-21 15:03:34 +02:00
gwen
45b9a5495e delete httpx requirement 2024-10-16 13:25:30 +02:00
gwen
19ecc56316 replace httpx with the requests stdlib 2024-10-16 13:24:14 +02:00
gwen
0ce1550028 tutorial : mode_proxy variable 2024-10-15 18:17:21 +02:00
gwen
cd850f1089 questionary, user data, output 2024-10-15 17:49:26 +02:00
gwen
a310b6f063 typo 2024-10-15 16:55:01 +02:00
gwen
50299203af preliminaries 2024-10-15 15:50:01 +02:00
gwen
080bb9c489 links to the tutorial 2024-10-15 09:20:01 +02:00
gwen
d47011efe4 working dictionary link 2024-10-15 07:31:31 +02:00
gwen
9a84e28765 exticlude directive 2024-10-14 22:51:05 +02:00
gwen
c244a816f3 httpx 2024-10-14 19:28:12 +02:00
gwen
9199631ea5 extinclude attempt 2024-10-14 19:17:44 +02:00
2d02da9939 feat: better errors messages and improvement 2024-10-14 14:14:01 +02:00
06bb8f8fad feat: better debugging 2024-10-14 14:14:01 +02:00
e2eb58664a feat: update upgrade module 2024-10-14 14:14:01 +02:00
2c0763c516 feat: auto add multi for leader 2024-10-14 14:14:00 +02:00
ccb4eff30b Quick definition of an optional parameter variable (#24)
Co-authored-by: gwen <gwenaelremond@free.fr>
Co-authored-by: gwen <gwenael.remond@free.fr>
Co-authored-by: Emmanuel Garette <egarette@silique.fr>
Reviewed-on: #24
Co-authored-by: gremond <gwenael.remond@protonmail.com>
Co-committed-by: gremond <gwenael.remond@protonmail.com>
2024-10-14 14:14:00 +02:00
7bdc39e370 convert integer to string for port variable (#31 and #32)
Reviewed-on: #32
2024-10-14 14:14:00 +02:00
ea93bc55fc copy type/params and multi if default value is a variable calculation (#9, #31 and #34)
Reviewed-on: #34
Co-authored-by: Emmanuel Garette <egarette@silique.fr>
Co-committed-by: Emmanuel Garette <egarette@silique.fr>
2024-10-14 14:14:00 +02:00
ec86795768 fix: type is not always mandatory 2024-10-14 14:14:00 +02:00
810d08822b feat: use suffix in property calculation 2024-10-14 14:14:00 +02:00
83840e329c fix: auto_save in follower is now allowed in tiramisu 2024-10-14 14:14:00 +02:00
1389929371 feat: in structural commandline very is there is conflict alternative name 2024-10-14 14:14:00 +02:00
b21c13fea7 feat(#21): add examples attributes 2024-10-14 14:14:00 +02:00
a827ca2225 feat(#23): define easily a regexoption 2024-10-14 14:14:00 +02:00
75778cb39e corrections 2024-10-14 14:14:00 +02:00
9e614eb7a4 add 'whole' attribute to variable 2024-10-14 14:14:00 +02:00
90ef65dd40 toto 2024-10-14 14:14:00 +02:00
gwen
fd5b702686 add optional param in the schema
ref #22
2024-10-14 14:14:00 +02:00
d4167b1586 user_data is not mandatory if empty 2024-10-14 14:14:00 +02:00
161985ab50 add when and when_not attribute in properties calculation (#29) (#30)
Co-authored-by: Emmanuel Garette <egarette@silique.fr>
Co-authored-by: egarette@silique.fr <egarette@silique.fr>
Co-committed-by: egarette@silique.fr <egarette@silique.fr>
2024-10-14 14:14:00 +02:00
908356495b add information in tiramisu option for doc 2024-10-14 14:14:00 +02:00
eb561f5f52 allow 'variable' in dynamic family in 1.1 2024-10-14 14:14:00 +02:00
539ecc7412 new tiramisu's version 2024-10-14 14:14:00 +02:00
gwen
b39b728a90 pre-commit developer docs 2024-10-14 14:14:00 +02:00
gwen
80e3b73785 tests: tests about version 2024-10-14 14:14:00 +02:00
gwen
a7c862cccd default dictionary yaml format version 2024-10-14 14:14:00 +02:00
e2d62211ec shorthand declaration (#20)
Co-authored-by: Emmanuel Garette <egarette@silique.fr>
Co-authored-by: gwen <gwenaelremond@free.fr>
Reviewed-on: #20
Co-authored-by: gnunux@silique.fr <gnunux@silique.fr>
Co-committed-by: gnunux@silique.fr <gnunux@silique.fr>
2024-10-14 14:14:00 +02:00
9705830e88 Reactive error tests 2024-10-14 14:14:00 +02:00
0a9d1b3014 feat: add relative path support 2024-10-14 14:14:00 +02:00
gwen
4f33b7c7af feat: add default inference for basic types 2024-10-14 14:14:00 +02:00
bdd56d31c8 feat: suffixes in dynamic family should be a jinja function (#5)
Co-authored-by: Emmanuel Garette <egarette@silique.fr>
Co-authored-by: gwen <gwenaelremond@free.fr>
Reviewed-on: #5
Co-authored-by: egarette@silique.fr <egarette@silique.fr>
Co-committed-by: egarette@silique.fr <egarette@silique.fr>
2024-10-14 14:14:00 +02:00
gwen
6f9a981da7 update tutorial 2024-10-14 14:05:23 +02:00
gwen
4c6f451045 explicit link 2024-03-05 14:49:57 +01:00
gwen
fc8c1ecfda typo 2024-03-05 14:49:57 +01:00
gwen
7f7fe5f08f typos 2024-03-05 14:49:57 +01:00
615 changed files with 9243 additions and 7195 deletions

View file

@ -1,60 +1,3 @@
## 1.1.1 (2024-11-06)
### Fix
- upgrade tests
## 1.1.1rc0 (2024-11-06)
### Fix
- update tiramisu dependency
- better user information if a needed package is not installed
- **37**: import doesn't works for some python version
- **36**: format 1.0: suffix attribut must works
- update fr/rougail.mo
## 1.1.0 (2024-11-01)
### Fix
- black
- add changelog_merge_prerelease to commitizen
## 1.1.0rc0 (2024-11-01)
### Feat
- update dependencies
- translation
- in calculation better debugging
- can sort dictionaries in different directories
- suffix to identifier
- better errors messages and improvement
- better debugging
- update upgrade module
- auto add multi for leader
- use suffix in property calculation
- in structural commandline very is there is conflict alternative name
- **#21**: add examples attributes
- **#23**: define easily a regexoption
- add relative path support
- add default inference for basic types
- suffixes in dynamic family should be a jinja function (#5)
- we should be able to customize a new variable type
### Fix
- support for unknown language
- license
- README
- use black
- valid mode even if there is no mode configured
- valid mode if no mode defined
- update tests
- type is not always mandatory
- auto_save in follower is now allowed in tiramisu
## 1.0.2 (2024-01-28)
### Fix

326
LICENSE
View file

@ -1,165 +1,235 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
Preamble
0. Additional Definitions.
The GNU Affero General Public License is a free, copyleft license for software and other kinds of works, specifically designed to ensure cooperation with the community in the case of network server software.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, our General Public Licenses are intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
Developers that use our General Public Licenses protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License which gives you legal permission to copy, distribute and/or modify the software.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
A secondary benefit of defending all users' freedom is that improvements made in alternate versions of the program, if they receive widespread use, become available for other developers to incorporate. Many developers of free software are heartened and encouraged by the resulting cooperation. However, in the case of software used on network servers, this result may fail to come about. The GNU General Public License permits making a modified version and letting the public access it on a server without ever releasing its source code to the public.
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The GNU Affero General Public License is designed specifically to ensure that, in such cases, the modified source code becomes available to the community. It requires the operator of a network server to provide the source code of the modified version running there to the users of that server. Therefore, public use of a modified version, on a publicly accessible server, gives the public access to the source code of the modified version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
An older license, called the Affero General Public License and published by Affero, was designed to accomplish similar goals. This is a different license, not a version of the Affero GPL, but Affero has released a new version of the Affero GPL which permits relicensing under this license.
1. Exception to Section 3 of the GNU GPL.
The precise terms and conditions for copying, distribution and modification follow.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
TERMS AND CONDITIONS
2. Conveying Modified Versions.
0. Definitions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
"This License" refers to version 3 of the GNU Affero General Public License.
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
"Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks.
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
"The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations.
3. Object Code Incorporating Material from Library Header Files.
To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
A "covered work" means either the unmodified Program or a work based on the Program.
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying.
4. Combined Works.
An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
1. Source Code.
The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work.
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those
subprograms and other parts of the work.
d) Do one of the following:
The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source.
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
The Corresponding Source for a work in source code form is that same work.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
2. Basic Permissions.
All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you.
5. Combined Libraries.
Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures.
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program.
6. Revised Versions of the GNU Lesser General Public License.
You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions:
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
a) The work must carry prominent notices stating that you modified it, and giving a relevant date.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.
b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices".
c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so.
A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways:
a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b.
d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d.
A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product.
"Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made.
If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM).
The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or authors of the material; or
e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors.
All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11).
However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice.
Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party.
If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it.
A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program.
13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the Program, your modified version must prominently offer all users interacting with it remotely through a computer network (if your version supports such interaction) an opportunity to receive the Corresponding Source of your version by providing access to the Corresponding Source from a network server at no charge, through some standard or customary means of facilitating copying of software. This Corresponding Source shall include the Corresponding Source for any work covered by version 3 of the GNU General Public License that is incorporated pursuant to the following paragraph.
Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the work with which it is combined will remain governed by version 3 of the GNU General Public License.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of the GNU Affero General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU Affero General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU Affero General Public License, you may choose any version ever published by the Free Software Foundation.
If the Program specifies that a proxy can decide which future versions of the GNU Affero General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program.
Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If your software can interact with users remotely through a computer network, you should also make sure that it provides a way for users to get its source. For example, if your program is a web application, its interface could display a "Source" link that leads users to an archive of the code. There are many ways you could offer source, and different solutions will be better for different programs; see section 13 for the specific requirements.
You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU AGPL, see <http://www.gnu.org/licenses/>.

View file

@ -28,27 +28,20 @@ Create the file `dict/dictionary.yml`:
```yml
---
version: 1.1
version: '1.0'
# describe a variable my_first_variable
# and a family with a variable my_second_variable
my_first_variable: my_value
my_first_variable:
default: my_value
my_family:
my_second_variable: 1
my_second_variable:
type: number
mandatory: true
value: 1
```
## Generate variable
### With commandline:
```bash
# rougail -m dict
Variables:
┣━━ 📓 my_first_variable: my_value
┗━━ 📂 my_family
┗━━ 📓 my_second_variable: 1
```
### With default value:
Here is a python3 example file:
@ -58,16 +51,19 @@ from rougail import Rougail, RougailConfig
from pprint import pprint
RougailConfig['dictionaries_dir'] = ['dict']
RougailConfig['templates_dir'] = ['tmpl']
RougailConfig['tmp_dir'] = 'tmp'
RougailConfig['destinations_dir'] = 'dest'
rougail = Rougail()
config = rougail.run()
config = rougail.get_config()
pprint(config.value.get(), sort_dicts=False)
```
The result is:
```json
{<TiramisuOption path="rougail">: {<TiramisuOption path="rougail.my_first_variable">: 'my_value',
<TiramisuOption path="rougail.my_family">: {<TiramisuOption path="rougail.my_family.my_second_variable">: 1}}}
{'rougail.my_first_variable': 'my_value',
'rougail.my_family.my_second_variable': 1}
```
### With modified value
@ -80,6 +76,9 @@ from rougail import Rougail, RougailConfig
from pprint import pprint
RougailConfig['dictionaries_dir'] = ['dict']
RougailConfig['templates_dir'] = ['tmpl']
RougailConfig['tmp_dir'] = 'tmp'
RougailConfig['destinations_dir'] = 'dest'
rougail = Rougail()
config = rougail.get_config()
config.option('rougail.my_first_variable').value.set('modified_value')
@ -90,8 +89,8 @@ pprint(config.value.get(), sort_dicts=False)
The destination file is generated with new values:
```json
{<TiramisuOption path="rougail">: {<TiramisuOption path="rougail.my_first_variable">: 'modified_value',
<TiramisuOption path="rougail.my_family">: {<TiramisuOption path="rougail.my_family.my_second_variable">: 2}}}
{'rougail.my_first_variable': 'modified_value',
'rougail.my_family.my_second_variable': 2}
```
# Link

937
caracteristique.md Normal file
View file

@ -0,0 +1,937 @@
---
gitea: none
include_toc: true
---
# Rougail : caractéristiques
Rougail est un outil qui permet de gérer le cycle de vie des variables et de gestion des valeurs de ces variables.
Au moment de la conception de Rougail, il y a eu des choix structurant qui ont défini le fonctionnement de l'outil.
Voici la liste des principales caractéristiques :
## Logiciel libre
Rougail est un logiciel libre (et Open Source) de gestion externe de variables.
Le développement est réalisé de manière ouverte.
Rougail repose sur le logiciel libre Tiramisu comme moteur de contrainte.
Rougail est composé :
- d'un format de description
- d'un projet principal "Rougail" qui :
- charge les fichiers de structures,
- applique les valeurs des données utilisateur,
- représente les variables avec leurs valeurs.
- différents sous projet pour étendre les fonctionnalités de base
- un outil en ligne de commande pour facilité l'utilisation de la bibliothèque.
## Les acteurs
Rougail est destiné a séparer le cycle de vie de la variable entre 3 acteurs :
- l'acteur qui défini les variables
- l'acteur qui adapte les valeurs
- l'acteur qui utilise les variables avec leurs valeurs
Exemples concret d'acteur :
Dans le cadre du déploiement d'une configuration :
- l'intégrateur défini les variables
- l'exploitant adapte les valeurs
- l'installeur utilise les variables avec leurs valeurs
Dans le cadre du développement d'une application :
- le dévelopeur défini les variables
- l'utilisateur adapte les valeurs
- l'application utilise les variables avec leurs valeurs
|------------------------------------------|
| Acteur |
|------------------------------------------|
| défini les variables |
| adapte les valeurs |
| utilise les variables avec leurs valeurs |
|------------------------------------------|
## Cycle de vie des variables
Le but de Rougail est de gérer les variables et plus largement la configuration.
Rougail a pour but de définir les variables puis de gérer tout son cycle de vie.
Dans le cycle de vie d'une variable, on inclut les étapes générique d'une variable (par exemple pour le langage C) :
- déclaration (elles se voient attribuer un nom et un type) ;
- définition (elles se voient affecter leur première valeur) ;
- affectation (elles se voient modifier la valeur de la variable) ;
- lecture (elles se voient utilisent la valeur de la variable) ;
- fin de vie (elles se terminent à la destruction de l'objet).
Mais d'autres notions sont inclusent dans le cycle de vie :
- extension de la déclaration
- la "description" de la variable
- présentation, l'acteur qui adapte les valeurs doit avoir toutes les informations nécessaires pour renseigner les valeurs, c'est ainsi qu'il est possible de généré automatiquement la documentation, le journal des modifications, ...
- autorisations, des propriétés décrivent les contraintes d'accès
- spécialisation, qui décrit l'usage possible d'une variable
Rôle des différents composants Rougail :
Le format permet de décrire :
- la déclaration (nom, type, description, la présentation)
- les autorisations
- la spécialisation
Rougail et ces composants :
- la déclaration (via Tiramisu)
- la présentation
- l'affectation (via Tiramisu)
- la lecture
- la fin de vie
|------------------------------------------|----------------------------|
| Acteur | Cycle de vies |
|------------------------------------------|----------------------------|
| défini les variables | déclaration + présentation |
| adapte les valeurs | affectration |
| utilise les variables avec leurs valeurs | lecture |
| | destruction |
|------------------------------------------|----------------------------|
## Architecture Structurals-UserDatas-Outputs
### Structurals
Les fichiers de "Structures" sont des fichiers au format Rougail dans laquelle toutes les informations du cycle de vie sont définies.
Il est important de tout mettre ces informations au même endroit. Ainsi on connaitre toutes les aspects de la variable en lisant ces fichiers.
On y retrouve :
- un schéma
- la définition des contraintes
- des éléments de documentation
- ...
Les variables sont ici mutables, elles peuvent être redéfinit à tout moment.
Ces fichiers sont créées par l'acteur qui défini les variables.
### UserDatas
Une fois la structure définie, il est possible de charger les "Données Utilisateur" (UserDatas). On retrouve plusieurs type de données utilisateurs :
- des fichiers de configuration
- des variables d'environnement
- des sources externes
- des options de lignes de commande
- un formulaire
- ...
Les variables sont ici immuables, par contre les valeurs sont mutables.
Ces sources sont peuplées par l'acteur qui adapte les valeurs.
### Outputs
Ensuite on pourra définir sous quelle forme on veut recueillir l'information (Outputs) :
- un objet Tiramisu
- une extraction JSON
- un export pour l'inventaire Ansible
- de la documetation
- ...
Les variables et les valeurs sont ici immuables.
Voici un exemple concret :
Un intégrateur a besoin d'une variable et défini la défini ainsi (dans le fichier `structure_architecture.yml`) :
```yaml
%YAML 1.2
---
version: 1.1
my_variable:
...
```
L'exploitant défini la valeur de cette variable ainsi (dans le fichier `userdata.yml`) :
```yaml
---
my_variable: a value
```
On désire afficher de manière lisible la configuration :
```bash
rougail -m structure_architecture.yml -u yaml -yf userdata.yml
╭──────── Caption ────────╮
│ Variable Modified value │
╰─────────────────────────╯
Variables:
┗━━ 📓 my_variable: a value ◀ loaded from the YAML file "userdata.yml"
```
|-------------|------------------------------------------|----------------------------|----------|----------|
| Étape | Acteur | Cycle de vies | Variable | Valeur |
|-------------|------------------------------------------|----------------------------|----------|----------|
| Structurals | défini les variables | déclaration + présentation | mutable | |
| UserDatas | adapte les valeurs | affectration | immuable | mutable |
| Outputs | utilise les variables avec leurs valeurs | lecture | immuable | immuable |
| | | destruction | | |
|-------------|------------------------------------------|----------------------------|----------|----------|
### Valeur par défaut
Les variables ont une valeur. Une valeur par défaut est définit à la variable (None ou []) mais il est possible d'en définir une autre.
Il ne faut pas confondre la valeur par défaut ou la/les valeur(s) défini par l'acteur adaptant la configuration.
Par exemple, si je veux définir la variable my_variable en y spécifiant une valeur par défaut (dans le fichier `structure_default.yml`) :
```yaml
%YAML 1.2
---
version: 1.1
my_variable:
default: a default value
...
```
```bash
rougail -m structure_default.yml
╭─────── Caption ────────╮
│ Variable Default value │
╰────────────────────────╯
Variables:
┗━━ 📓 my_variable: a default value
```
|-------------|------------------------------------------|----------------------------|----------|----------|
| Étape | Acteur | Cycle de vies | Variable | Valeur |
|-------------|------------------------------------------|----------------------------|----------|----------|
| Structurals | défini les variables | déclaration + présentation | mutable | défaut |
| UserDatas | adapte les valeurs | affectration | immuable | mutable |
| Outputs | utilise les variables avec leurs valeurs | lecture | immuable | immuable |
| | | destruction | | |
|-------------|------------------------------------------|----------------------------|----------|----------|
## Le format : un langage
### DSL (Domain Specific Language)
Contrairement à un langage générique, un langage dédié est conçu pour répondre à un domaine d'application précis.
La description des variables se faire dans des fichiers YAML version 1.2. Le langage de configuration de Rougail permet de décrire tout le cycle de vie de la variable.
### Abstraction déclarative
Les variables sont décrite suivant un modèle déclaratif. Cela signigie que l'utilisateur définit simplement l'état final souhaité de celle-ci. Tiramisu determinera les actions nécessaires pour atteindre cet état.
Dit autrement l'utilisateur défini le schéma des variables qui sera ensuite appliqué de manière déterministe.
### Redéfinition explicite
Les variables peuvent être redéfinis à tout moment (utile notamment lorsqu'on définit des modèles de configuration). Mais la redéfinition d'une variable doit être explicitement déclaré comme tel.
Par exemple, si je veux redéfinir la variable my_variable en y spécifiant une valeur par défaut (dans le fichier `structure_redefine.yml`) :
```yaml
%YAML 1.2
---
version: 1.1
my_variable:
redefine: true
default: a new default value
...
```
```bash
rougail -m structure_default.yml structure_redefine.yml
╭─────── Caption ────────╮
│ Variable Default value │
╰────────────────────────╯
Variables:
┗━━ 📓 my_variable: a new default value
```
## Typage
### Type standard
Rougail (et Tiramisu) accepte les types standards suivant :
- string (type par défaut d'une variable)
- integer
- float
- boolean
### Type métier
Mais on va retrouver également tout une série de type métier :
- IP
- domainname
- port
- MAC
- choice
- secret
- ...
Voici quelques exemples (dans le fichier `structure_type.yml`) :
```yaml
%YAML 1.2
---
version: 1.1
my_integer: 1
my_mail:
type: mail
default: foo@bar.net
my_date:
type: date
default: "2025-11-19"
...
```
```bash
rougail -m structure_type.yml
╭─────── Caption ────────╮
│ Variable Default value │
╰────────────────────────╯
Variables:
┣━━ 📓 my_integer: 1
┣━━ 📓 my_mail: foo@bar.net
┗━━ 📓 my_date: 2025-11-19
```
### Fortement typé
Rougail utilise en interne la bibliothèque Tiramisu. Les variables dans Tiramisu sont fortement typé.
C'est à dire que le chargement des données utilisateur implique une attention sur le type des variables. Pour les données utilisateurs non typées (comme les variables d'environnement), en pré traitement, il y aura une adaptation du type de la valeur.
Par exemple si l'exploitant adapte la valeur de cette variable ainsi (dans le fichier userdata_fort_type.yml :
```yaml
---
my_variable: 1
```
La valeur ne pourra pas être chargée (le type par défaut étant le type "string") :
```bash
rougail -m structure_architecture.yml -u yaml -yf userdata_fort_type.yml
🔔 WARNINGS
┗━━ the value "1" is an invalid string for "my_variable", which is not a string, it will be ignored when loading from the YAML file "userdata.yml"
🛑 ERRORS
┗━━ The following variables are mandatory but have no value:
┗━━ my_variable
```
### Typage dynamique
Au moment de la définition de la structure, le type est dynamique. C'est à dire que l'acteur qui défini la variable peut changé à tout moment le type de la variable.
Par exemple (dans le fichier `structure_redefine_type.yml`) :
```yaml
%YAML 1.2
---
version: 1.1
my_variable:
redefine: true
type: integer
...
```
```bash
rougail -m structure_architecture.yml structure_redefine_type.yml -u yaml -yf userdata_fort_type.yml
╭──────── Caption ────────╮
│ Variable Modified value │
╰─────────────────────────╯
Variables:
┗━━ 📓 my_variable: 1 ◀ loaded from the YAML file "userdata.yml"
```
Par contre, comme l'exemple du typage fort le suggère, l'acteur qui adapte la valeur n'a pas la possiblité de redéfinir le type de la variable.
### Inférence de type
Le type peut être défini explicitement (comme dans le fichier structure_type.yml) ou déduit du typage des variables YAML.
Par exemple la variable avec une valeur par défaut à 1 est une variable de type "integer" (a mettre dans le fichier `structure_inference.yml`) :
```yaml
%YAML 1.2
---
version: 1.1
my_variable: 1
...
```
```bash
rougail -m structure_inference.yml
╭─────── Caption ────────╮
│ Variable Default value │
╰────────────────────────╯
Variables:
┗━━ 📓 my_variable: 1
```
### Variable "nullable"
Le type "null" (ou "None" en python) n'existe pas dans Rougail. "null" est une valeur. Tous les types peuvent accepter cette valeur, mais par défaut, ce n'est pas le cas.
Voici la déclaration d'une variable avec la valeur par défaut à "null" (dans le fichier `structure_nullable.yml`) :
```yaml
%YAML 1.2
---
version: 1.1
my_variable:
...
```
```bash
rougail -m structure_nullable.yml
🛑 ERRORS
┗━━ The following variables are mandatory but have no value:
┗━━ my_variable
```
En réalité la variable n'est pas accessible lorsque Tiramisu est mode "lecture seule" (ce qui est le cas lors des étapes Outputs par défaut). Lorsqu'on force le mode "lecture écriture" on a bien accès :
```bash
rougail -m structure_nullable.yml --cli.read_write
╭─────── Caption ────────╮
│ Variable Default value │
╰────────────────────────╯
Variables:
┗━━ 📓 my_variable: null
```
Pour que notre variable accepte dans tous les cas "null" il faut modifier le fichier de structure comme cela :
```yaml
%YAML 1.2
---
version: 1.1
my_variable:
mandatory: false
...
```
```bash
rougail -m structure_nullable.yml --cli.read_write
╭─────── Caption ────────╮
│ Variable Default value │
╰────────────────────────╯
Variables:
┗━━ 📓 my_variable: null
```
### Variable "liste"
La liste n'est pas non plus un type. C'est une propriété d'une variable. Cela signifie qu'une liste ne peut pas contenir des valeurs de plusieurs types.
### Famille objet
Une famille est une variable d'un type particuiler. C'est un conteneur destiné à accueillir des variables.
#### Un objet
Ce qu'on appele "objet" généralement est appeler dans Rougail des "familles". Donc au lieu de déclarer mes variables à la racine, je vais la déclarer dans une famille.
Par exemple dans le fichier `structure_family.yml` je créé un famille my_object qui contient deux variables :
```yaml
%YAML 1.2
---
version: 1.1
my_object:
key1: value1
key2: value2
...
```
Si j'exporte au format JSON j'ai bien un objet :
```bash
rougail -m structure_family.yml -o json
{
"my_object": {
"key1": "value1",
"key2": "value2"
}
}
```
Les familles gèrent l'arborescence. Il est possible de faire des sous-familles.
#### Liste d'objet
Une famille particulière, appeler "leadership" permet d'avoir une liste d'objet identique.
Par exemple si je veux pouvoir créer un nombre non limité d'utilisateur associé à un mot de passe, je ne peux pas passer par des listes, je veux une liste d'objet.
Voici le contenu du fichier `structure_leadership.yml` :
```yaml
%YAML 1.2
---
version: 1.1
users:
type: leadership
username:
type: unix_user
password:
type: secret
...
```
Et le fichier `userdata_leadership.yml` :
```yaml
---
users:
- username: foo
password: SoSecr31
- username: bar
password: SoSecr31
- username: toot
password: SoSecr31
...
```
J'ai bien une liste d'objet :
```bash
rougail -m structure_leadership.yml -u yaml -yf userdata_leadership.yml -o json
{
"users": [
{
"username": "foo",
"password": "SoSecr31"
},
{
"username": "bar",
"password": "SoSecr31"
},
{
"username": "toot",
"password": "SoSecr31"
}
]
}
```
|-------------|------------------------------------------|----------------------------|----------|----------|------------|
| Étape | Acteur | Cycle de vies | Variable | Valeur | Propriétés |
|-------------|------------------------------------------|----------------------------|----------|----------|------------|
| Structurals | défini les variables | déclaration + présentation | mutable | défaut | |
| UserDatas | adapte les valeurs | affectration | immuable | mutable | |
| Outputs | utilise les variables avec leurs valeurs | lecture | immuable | immuable | mandatory |
| | | destruction | | | |
|-------------|------------------------------------------|----------------------------|----------|----------|------------|
## Intégrité des données
L'intégrité des données fait référence au fait que les données doivent être fiables et précises tout au long de leur cycle de vie.
Cela signifie que la valeur doit être :
- de qualité
- adapté au context globale
### Qualité de la donnée
Pour avoir des données de qualité, il faut que l'outil valide la saisie utilisateur.
La première façon de valider la donnée est bien évidement de définir le bon type.
Mais cela ne suffit pas.
#### Paramètres de type
Il existe, pour certains type, un certains nombres de paramètres qui vont pouvoir compléter le typage des variables (dans le fichier `structure_param_type.yml`) :
```yaml
%YAML 1.2
---
version: 1.1
my_percent:
type: integer
params:
min_integer: 0
max_integer: 100
default:
10
...
```
```bash
rougail -m structure_param_type.yml
╭─────── Caption ────────╮
│ Variable Default value │
╰────────────────────────╯
Variables:
┗━━ 📓 my_percent: 10
```
Mais avec une donnée utilisateur invalide, la valeur ne sera pas chargé (dans le fichier `userdata1.yml`) :
```yaml
---
my_percent: 120
```
```bash
rougail -m structure6.yml -u yaml -yf userdata1.yml
🔔 WARNINGS
┗━━ the value "120" is an invalid integer for "my_percent", value must be less than "100", it will be
ignored when loading from the YAML file "userdata1.yml"
╭─────── Caption ────────╮
│ Variable Default value │
╰────────────────────────╯
Variables:
┗━━ 📓 my_percent: 10
```
Ou pourra même généré une erreur :
```bash
rougail -m structure6.yml -u yaml -yf userdata1.yml --cli.invalid_user_datas_error
🛑 ERRORS
┗━━ the value "120" is an invalid integer for "my_percent", value must be less than "100", it will be
ignored when loading from the YAML file "userdata1.yml
```
#### Validation
Mais il est possible d'ajouter des validations complémentaires, par exemple ici vérifier que la variable est impaire (dans le fichier `structure_validators.yml`) :
```yaml
%YAML 1.2
---
version: 1.1
my_odd_variable:
type: integer
validators:
- jinja: "{% if not (_.my_odd_variable % 2) %}not an odd integer{% endif %}"
default:
10
...
```
```bash
rougail -m structure7.yml
🛑 ERRORS
┗━━ "10" is an invalid integer for "my_odd_variable", not an odd integer
```
Par contre cela passe avec une valeur impaire (dans le fichier `userdata_validators.yml`) :
```yaml
---
my_odd_variable: 11
```
```bash
rougail -m structure_validators.yml -u yaml -yf userdata_validators.yml
╭────────────── Caption ───────────────╮
│ Variable Modified value │
│ (⏳ Original default value) │
╰──────────────────────────────────────╯
Variables:
┗━━ 📓 my_odd_variable: 11 ◀ loaded from the YAML file "userdata_validators.yml" (⏳ 10)
```
### Cohérence globale
Une variable isolée peut être considéré comme étant de qualité mais devenir incohérence suivant le contexte.
Par exemple, si on demande une valeur minimum puis une valeur maximum, la minimal doit être inférieur à la maximal. Dans le fichier `structure_consistency.yml` on a :
```yaml
%YAML 1.2
---
version: 1.1
my_min_value: 11
my_max_value:
default:
variable: _.my_min_value
validators:
- jinja: |-
{% if _.my_min_value >= _.my_max_value %}
must but upper than {{ _.my_min_value }} (the value of "my_min_value")
{% endif %}
...
```
```bash
rougail -m structure_consistency.yml
🛑 ERRORS
┗━━ "11" is an invalid integer for "my_max_value", must but upper than 11 (the value of "my_min_value")
```
Si on défini la bonne valeur :
```yaml
---
my_max_value: 13
```
```bash
%YAML 1.2
---
version: 1.1
my_min_value: 11
my_max_value:
default:
variable: _.my_min_value
validators:
- jinja: |-
{% if _.my_min_value >= _.my_max_value %}
must but upper than {{ _.my_min_value }} (the value of "my_min_value")
{% endif %}
...
```
```bash
rougail -m structure_consistency.yml -u yaml -yf userdata_consistency.yml
╭────────────── Caption ───────────────╮
│ Variable Default value │
│ Modified value │
│ (⏳ Original default value) │
╰──────────────────────────────────────╯
Variables:
┣━━ 📓 my_min_value: 11
┗━━ 📓 my_max_value: 13 ◀ loaded from the YAML file "userdata_consistency.yml" (⏳ 11)
```
## Contrôle des accès
Le contrôle des accès est réalisé dès qu'on essaye d'accéder à une variable.
Dans Tiramisu il existe deux modes d'accès aux variables par défaut :
- le mode "lecture écriture" : ce mode est utilisé au moment du chargement des données utilisateurs
- le mode "lecture seule" : ce mode est utilisé au moment de le représentation des valeurs
Il existe deux grands type de contrôle des accès :
- les modes
- les propriétés
On pourrait rajouter les étiquettes à cette liste, même si l'objectif n'est pas spécialement de contrôler les accès via les étiquettes.
### Les modes
On va partir sur un cas classic de définition des modes. On part sur trois mode :
- basic : de façon automatique les variables obligatoires sans valeur par défaut (dans ce cas l'acteur qui adapte la configuration devra obligatoirement renseigné des valeurs) et manuellement les variables que l'acteur qui définit les variables juge utile
- standard : automatique les autres variables
- advanced : les variables que l'acteur qui définit les variables décide de mettre
Par exemple créons deux variables dans `structure_mode.yml` avec des modes différents :
```yaml
%YAML 1.2
---
version: 1.1
standard_variable: default value
advanced_variable:
mode: advanced
default: default value
...
```
Avec le fichier `userdata_mode.yml` :
```yaml
---
standard_variable: value
advanced_variable: value
```
```yaml
rougail -m structure_mode.yml -u yaml -ff userdata_mode.yml --modes_level basic standard advanced
╭────────────── Caption ───────────────╮
│ Variable Modified value │
│ (⏳ Original default value) │
╰──────────────────────────────────────╯
Variables:
┣━━ 📓 standard_variable: value ◀ loaded from the YAML file "userdata_mode.yml" (⏳ default value)
┗━━ 📓 advanced_variable: value ◀ loaded from the YAML file "userdata_mode.yml" (⏳ default value)
```
Et interdisont aux acteurs adaptant la configuration de modifier les variables "advanced" :
```bash
rougail -m structure_mode.yml -u yaml -yf userdata_mode.yml --modes_level basic standard advanced --cli.inaccessible_read_write_modes advanced
🔔 WARNINGS
┗━━ variable "advanced_variable" is advanced, it will be ignored when loading from the YAML file "userdata_mode.yml"
╭───────────────────── Caption ─────────────────────╮
│ Variable Default value │
│ Undocumented variable Modified value │
│ (⏳ Original default value) │
╰───────────────────────────────────────────────────╯
Variables:
┣━━ 📓 standard_variable: value ◀ loaded from the YAML file "userdata_mode.yml" (⏳ default value)
┗━━ 📓 advanced_variable: default value
```
La gestion des modes est plutôt limités via la ligne de commande. Pour des besoins plus spécifique il sera nécessaire de passer par la bibliothèque.
### Les propriétés
Les deux propriétés importantes sont :
- les variables cachée
- les variables désactivée
Ces propriétés peuvent être fixe ou calculer suivant le contexte
#### Variable cachée
Une variable cachée est une variable qui ne sera pas modifiable par l'utilisateur et que ne sera pas présente en mode "lecture écriture" mais présente en mode "lecture seule".
Généralement on utilise ce type de variable lorsqu'on ne veut pas que l'acteur qui adapte la configuration puisse modifier cette variable ou alors parce que la valeur de la variable est issu d'un calcul.
#### Variable désactivée
Une variable désactiver est une variable qui sera accessible a aucun des acteurs.
On utilise généralement cette propriété de façon dynamique pour supprimer l'accès à cette variable suivant le context.
Prenons un exemple de variable cachée (donc non modifiable) et ajoutons un variable "use_proxy" qui permet de définir une adresse proxy si on le souhait. Pour cela nous allons créer le fichier suivante : `structure_properties.yml` :
```yaml
%YAML 1.2
---
version: 1.1
hidden_variable:
hidden: true
default: accessible
use_proxy: false
proxy_address:
disabled:
variable: use_proxy
when: false
...
```
```bash
rougail -m structure_properties.yml
╭────────────── Caption ──────────────╮
│ Variable Default value │
│ Unmodifiable variable │
╰─────────────────────────────────────╯
Variables:
┣━━ 📓 hidden_variable: accessible
┗━━ 📓 use_proxy: false
```
Essayons de modifier la variable cachée et de configurer le proxy dans le fichier `userdata_properties.yml` :
```yaml
---
hidden_variable: inaccessible
use_proxy: true
proxy_address: https://proxy.foo.net:4128/
```
```yaml
rougail -m structure_properties.yml -u yaml -ff userdata_properties.yml
🔔 WARNINGS
┗━━ variable "hidden_variable" is hidden, it will be ignored when loading from the YAML file "userdata_properties.yml"
╭───────────────────── Caption ─────────────────────╮
│ Variable Default value │
│ Unmodifiable variable Modified value │
│ (⏳ Original default value) │
╰───────────────────────────────────────────────────╯
Variables:
┣━━ 📓 hidden_variable: accessible
┣━━ 📓 use_proxy: true ◀ loaded from the YAML file "userdata_properties.yml" (⏳ false)
┗━━ 📓 proxy_address: https://proxy.foo.net:4128/ ◀ loaded from the YAML file "userdata_properties.yml"
```
|-------------|------------------------------------------|----------------------------|----------|----------|--------------------|----------------------|
| Étape | Acteur | Cycle de vies | Variable | Valeur | Lecture | Écriture | Propriétés |
|-------------|------------------------------------------|----------------------------|----------|----------|--------------------|----------------------|
| Structurals | défini les variables | déclaration + présentation | mutable | défaut | | |
| UserDatas | adapte les valeurs | affectration | immuable | mutable | lecture + écriture | hidden + disabled |
| Outputs | utilise les variables avec leurs valeurs | lecture | immuable | immuable | lecture | disabled + mandatory |
| | | destruction | | | | |
|-------------|------------------------------------------|----------------------------|----------|----------|--------------------|----------------------|

2
docs/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
.venv/
build/

20
docs/Makefile Normal file
View file

@ -0,0 +1,20 @@
# 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)

73
docs/_static/terminal.css vendored Normal file
View 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 */
}

View file

@ -1,2 +0,0 @@
variable
<https://en.wikipedia.org/wiki/Variable_(computer_science)>`_

View file

@ -230,7 +230,7 @@ An example with an index type parameter:
- val1
- val2
follower1:
type: number
type: integer
validators:
- type: jinja
jinja: |

58
docs/cli.rst Normal file
View file

@ -0,0 +1,58 @@
:orphan:
The Rougail Command Line Interface
========================================
Standard usage
-----------------
::
rougail -m firefox/ -u yaml -yf config/02/config.yml
::
rougail --cli.versions
tiramisu: 5.2.0a9
tiramisu-cmdline-parser: 0.7.0a1
rougail: 1.2.0a29
rougail-cli: 0.2.0a19
rougail-user-data-environment: 0.1.0a9
rougail-user-data-yaml: 0.2.0a11
rougail-output-console: 0.2.0a11
rougail-output-json: 0.2.0a8
::
env ROUGAIL_MANUAL.USE_FOR_HTTPS=true rougail -m structfile/proxy2.yml -u yaml environment --yaml.filename userdata/proxy.yml -o json
::
env ROUGAIL_MANUAL.USE_FOR_HTTPS=true rougail -m structfile/proxy2.yml -u yaml --yaml.filename userdata/proxy.yml -o json --json.get manual.https_proxy --json.read_write
{
"address": "toto.fr",
"port": "8888"
}
Invalid user data error
-----------------------------
::
rougail -m firefox/ -u yaml -yf config/02/config.yml --cli.invalid_user_data_error
Unknown user data error
-----------------------------
l'option "`--cli.unknown_user_data_error`", ca ressemble à "`--cli.invalid_user_data_error`" mais ca concerne là les variables inconnues (ou ici désactivé ou hidden mais ca reviens au meme pour moi) chargés dans des user data.
https://forge.cloud.silique.fr/stove/rougail-tutorials/src/commit/v1.1_050/README.md#output-when-unknown-user-data-is-an-error
.. note:: The `--cli.unknown_user_data_error` option changes the behaviour of the Rougail CLI's standard output:
when an unknown (or disabled or hidden) variable is declared in the :term:`user data file <user data>`
then it appears in the output as an error instead of a warning.

44
docs/concepts.rst Normal file
View file

@ -0,0 +1,44 @@
Abstract presentation
=========================
Why another validating library?
-------------------------------------
Using Rougail in your application or your python libraries can tansform end user consumer defined consistency rules into highly consistent business objects.
We then have to say that the handling system used to ensure the variables integrity is another python library, called :term:`Tiramisu`. Rougail is currently strongly affiliated with Tiramisu.
.. note:: Rougail is currently intended to work in coordination with :term:`Tiramisu` and **is not** intended to be connected with any other consistency handling system.
Explained differently, Rougail allows you to easily implement an integration of the powerful tiramisu consistency handling system.
What is a consistency handling system ?
-------------------------------------------
.. questions:: Rougail, Tiramisu: What is it all about?
**Question**: OK, I have understood that the Rougail library allows me to take advantage of the :xref:`tiramisu` consistency handling library. But what is all this for? What is exactly a consistency handling system? And again, what is this :xref:`Tiramisu library <tiramisu library>` used for?
**Answer**: Well, we will explain in details what this :xref:`tiramisu` library is and what Rougail is.
In (very) short:
- Rougail is the YAML consistency description of a :term:`context`\ 's situation
- Tiramisu is the consistency engine linter
.. glossary::
Tiramisu
:xref:`tiramisu` is a consistency handling system that has initially been designed
in the configuration management scope. Until now,
this library is generally used to handle configuration options.
It manages variables and group of variables. In the Tiramisu scope we call
it *options* and *option descriptions*.
Here is the :xref:`tiramisu documentation <tiramisu>`.
In the Rougail scope, we call it :term:`variables <variable>` and :term:`families <family>`.
In Rougail, the families and variables are located in the :term:`structure files <structure file>`.

View file

@ -1,24 +1,20 @@
# 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 --------------------------------------------------------------
# If extensions (or modules to document with autodoc) are in another directory,
# 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
import sys, os
# 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 = 'Rougail'
copyright = '2019-2023, Silique'
copyright = '2019-2025, Silique'
author = 'gwen'
# The short X.Y version
@ -33,55 +29,64 @@ release = '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 = [
'sphinx.ext.extlinks', 'sphinx_lesson',
#'myst_parser', 'sphinx.ext.extlinks'
'sphinx.ext.extlinks', 'sphinx_lesson', 'sphinx.ext.todo',
'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",
#]
#---- disable highlight warnings with yaml new version ----
# Configuration pour les blocs de code
highlight_language = 'yaml'
# Options spécifiques pour YAML
highlight_options = {
'yaml': {
'startinline': True
}
}
suppress_warnings = [
'misc.highlighting_failure'
]
#---- 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',
# enables syntax like :proxy:`my source <hello>` in the src files
extlinks = {'proxy': ('/proxy/%s.html',
'external link: ')}
# enables syntax like
" :source:`v1.1_010/firefox/00-proxy.yml` "
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'),
'tutorial': ('https://forge.cloud.silique.fr/stove/rougail-tutorials/%s', 'tutorial %s'),
}
#---- options for HTML output ----
default_role = "code"
html_theme = "sphinx_rtd_theme"
pygments_style = 'sphinx'
html_short_title = "Rougail"
html_title = "Rougail documenation"
# If true, links to the reST sources are added to the pages.
html_show_sourcelink = False
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
html_show_sphinx = False
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is 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.
templates_path = ['_templates']
@ -109,41 +114,8 @@ language = 'en'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# 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):
app.add_css_file('css/custom.css')

View file

@ -1,19 +0,0 @@
Dictionary conventions
=========================
Dictionary file naming convention
------------------------------------
The order of dictionaries is important for the order in which variables and families are created.
The files must therefore be started with two numbers followed by a hyphen.
For example: `00-base.xml`
Naming convention for families and variables
-----------------------------------------------
The only restriction on the name of families and variables is that the name must not start with the `"_"` (undescore) character.
However, it is preferable to only use lowercase ASCII letters, numbers and the `"_"` (undescore) character.
The snake case typographic convention is therefore used.

View file

@ -1,36 +0,0 @@
The dictionaries
=====================
What do you mean by :term:`dictionary`?
-------------------------------------------
A :term:`dictionary` 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`.
:term:`Families` and :term:`variables` can be defined in several dictionaries. These dictionaries are then aggregated.
Dictionaries are loaded in the directory order defined by the `dictionaries_dir` configuration parameter.
Each directory is loaded one after the other.
Inside these directories the YAML files will be classified in alphabetical order.
There is no alphabetical ordering of all YAML files in all directories.
It is also possible to :term:`redefine` elements to change the behavior of a family or a variable.
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>`.
This namespace is a bit special, it can access variables in another namespace.
The extra dictionaries
---------------------------
An extra is a different namespace. The idea is to be able to classify the variables by theme.
Extra namespaces must be declared :doc:`when configuring Rougail <configuration>`.
In this namespace we cannot access variables from another `extra` namespace.
On the other hand, it is possible to access the variable of the default namespace.

42
docs/documentation.rst Normal file
View file

@ -0,0 +1,42 @@
Documentation standards
==========================
YAML bloc code
-------------------
If you have some YAML
The rougail YAML follows the YAML 1.2 conventions,
you might encounter a warning like this one::
WARNING: Le lexème du bloc_littéral ' %YAML 1.2\n ---\n version: 1.1\n\n ...'
en tant que "yaml" a entraîné une erreur au niveau du jeton : '%'.
Réessayer en mode relaxé.
.. code-block:: rst
.. code-block:: yaml
:caption: the :file:`dist/00-base.yml` file content
%YAML 1.2
---
version: 1.1
my_variable: my_value_extra # a simple variable
...
Because the sphinx-doc tool is not YAML 1.2 ready yet.
The solution is simple, just escape the `%` like this:
.. code-block:: rst
.. code-block:: yaml
:caption: the :file:`dist/00-base.yml` file content
\%YAML 1.2
---
version: 1.1
my_variable: my_value_extra # a simple variable
...

95
docs/ext/extinclude.py Normal file
View file

@ -0,0 +1,95 @@
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
from requests.exceptions import RequestException
from docutils.parsers.rst import directives
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]
try:
headers = {
'accept': 'application/text',
'Content-Type': 'application/text',
}
response = requests.get(url, headers=headers)
response.raise_for_status() # This will raise an exception for 4xx/5xx status codes
except requests.exceptions.HTTPError as e:
if response.status_code == 404:
error_msg = f"extinclude: URL not found (404): {url}"
else:
error_msg = f"extinclude: HTTP error {response.status_code}: {url}"
# Create an error node that will be displayed in the documentation
error_node = nodes.error()
para = nodes.paragraph()
para += nodes.Text(error_msg)
error_node += para
self.state.document.reporter.warning(error_msg, line=self.lineno)
return [error_node]
except requests.exceptions.RequestException as e:
error_msg = f"extinclude: Failed to fetch URL {url}: {str(e)}"
# Create an error node that will be displayed in the documentation
error_node = nodes.error()
para = nodes.paragraph()
para += nodes.Text(error_msg)
error_node += para
self.state.document.reporter.warning(error_msg, line=self.lineno)
return [error_node]
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]
def setup(app: Sphinx) -> ExtensionMetadata:
app.add_directive('extinclude', ExtInclude)
return {
'version': '0.1',
'parallel_read_safe': True,
'parallel_write_safe': True,
}

67
docs/ext/xref.py Normal file
View file

@ -0,0 +1,67 @@
"""adds link url in the global scope
sample use:
:xref:`Tiramisu <tiramisu>`
You must declare in the `conf.py`
::
#---- 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)
"""
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)

View file

@ -1,19 +1,27 @@
A family
============
The families
=============
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.
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
----------------------------
@ -35,7 +43,7 @@ If you add comment in same line of name, this comment is use as description:
Parameters
---------------
.. FIXME: faire une page sur la "convention on variable names"
.. todo:: faire une page sur la "convention on variable names"
.. list-table::
:widths: 15 45

View file

@ -1,6 +1,8 @@
Calculated default values
==============================
.. _calculated_variable:
Synopsis
-----------
@ -48,11 +50,11 @@ Depending on the types of calculation, the parameters will be different:
`mandatory`
- Template Jinja. For a multiple variable, each line represents a value.
- `{% if rougail.variable %}
- `{% if rougail.variable %}`
{{ rougail.variable }}
`{{ rougail.variable }}`
{% endif %}`
`{% endif %}`
* - Jinja
- **params**
@ -170,6 +172,7 @@ Inside those namespaces we can add families and variables.
Here is an hierarchic examples:
.. code-block::
rougail
├── variable1
├── family1
@ -198,9 +201,9 @@ Inside a variable's `calculation` we can use relative path. "_" means that other
For example, in variable2's `calculation`, we can use relative path:
- __.variable1
- _.variable3
- __.family2.subfamily1.variable4
- `__.variable1`
- `_.variable3`
- `__.family2.subfamily1.variable4`
But we cannot access to extra1 variables with relative path.
@ -210,6 +213,7 @@ Dynamic family
Hire is a dynamic family "{{ suffix }}":
.. code-block::
rougail
├── variable1: ["val1", "val2"]
├── {{ suffix }}
@ -220,8 +224,8 @@ Hire is a dynamic family "{{ suffix }}":
For variable2's calculation, we can use:
- rougail.{{ suffix }}.variable3
- _.variable3
- `rougail.{{ suffix }}.variable3`
- `_.variable3`
In this case, we get value for "variable3" with the same suffix as "variable2".
@ -229,13 +233,13 @@ For variable4's calculation, we have two possibility:
- retrieves all values with all suffixes:
- rougail.{{ suffix }}.variable3
- __.{{ suffix }}.variable3
- `rougail.{{ suffix }}.variable3`
- `__.{{ suffix }}.variable3`
- retrieves a value for a specified suffix:
- rougail.val1.variable3
- __.val1.variable3
- `rougail.val1.variable3`
- `__.val1.variable3`
Examples
-----------
@ -266,14 +270,14 @@ Here is a second example with a boolean variable:
type: jinja
jinja: 'false'
And a multiple value of the number type:
And a multiple value of the integer type:
.. code-block:: yaml
---
version: '1.1'
my_calculated_variable:
type: number
type: integer
multi: true
default:
type: jinja
@ -543,7 +547,7 @@ Calculation via an index
- val1
- val2
follower1:
type: number
type: integer
default:
type: index

View file

@ -1,171 +1,73 @@
.. |Tiramisu| replace:: Tiramisu
.. _tiramisu: https://forge.cloud.silique.fr/stove/tiramisu
Getting started
====================
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?"
*Answer*: Well, let's explain what |Tiramisu| is and how we are using the |Tiramisu| library.
.. glossary::
Tiramisu
|Tiramisu| is a consistency handling system that has initially been designed
in the configuration management scope. To put it more simply,
this library is generally used to handle configuration options.
It manages variables and group of variables. In the Tiramisu scope we call
it *options* and *option descriptions*.
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`.
And this is what we are going to explain in this page.
The dictionaries
---------------------
.. glossary::
dictionary
dictionaries
A dictionary in the Rougail meaning is a YAML file that describes variables
and their dependencies / consistencies.
There can be a lot of dictionary files located in many different folders.
Rougail reads all the dictionaries and loads them into a single object
that handles the variables consistency.
.. image:: images/schema.png
The main advantage is that declaring variables and writing consistency is as simple
as writing YAML. With Rougail it is not necessary to write :term:`Tiramisu` code any more.
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 (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.
The YAML dictionaries 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.1'
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.
:orphan:
The variables
-----------------
Here is a :term:`structure file` example with only a variable **variable** named `proxy_mode`
A variable can be defined without other informations.
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_010/firefox/00-proxy.yml
:language: yaml
:caption: A Rougail structure file with a variable named `proxy_mode`, with a description.
:name: RougailStructureFirstVariable
..
%YAML 1.2
---
version: 1.1
proxy_mode:
...
But it's better to describe this variable:
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_011/firefox/00-proxy.yml
:language: yaml
:caption: A Rougail structure file with a variable named `proxy_mode`, with a description.
:name: RougailStructureFirstVariableWithDescription
..
%YAML 1.2
---
version: 1.1
proxy_mode: # 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 structure file with a variable named `proxy_mode`, with a default value.
:name: RougailStructureFirstVariableDefault
..
%YAML 1.2
---
version: 1.1
proxy_mode: No proxy # Configure Proxy Access to the Internet
...
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
in a application, but a variable represents something more that a configuration option.
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
--------------------------
.. glossary::
family
family
families
A :term:`family` is simply a container of variables and/ore some subfamilies.
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.
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:
Here how a YAML structure file with a family looks like:
.. code-block:: yaml
:caption: The `hello.yaml` file
---
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:
:caption: A :file:`hello.yml` structure sample file
---
version: '1.1'
@ -178,14 +80,3 @@ Let's continuing on our "Hello world" theme and add a :term:`family` container.
Here, we have a family named `world`.
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'}

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

BIN
docs/images/read_write.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 37 KiB

File diff suppressed because it is too large Load diff

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

View file

@ -8,40 +8,45 @@
Rougail
===========
.. 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
- 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).
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.
We then have to say that the handling system used to ensure the variables integrity is another python library, called :term:`Tiramisu`. Rougail is currently strongly affiliated with Tiramisu.
.. note:: Rougail is currently intended to work in coordination with :term:`Tiramisu` and **is not** intended to be connected with any other consistency handling system.
Explained differently, Rougail allows you to easily implement an integration of the powerful tiramisu consistency handling system.
- 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).
.. toctree::
:titlesonly:
:caption: Getting started
:caption: What is it all about
gettingstarted
tutorial
concepts
tutorial/index
.. toctree::
:titlesonly:
:caption: The library
:caption: The structured files
library
configuration
.. toctree::
:titlesonly:
:caption: The dictionaries
dictionary
dict_convention
structfile
naming_convention
.. toctree::
:titlesonly:
@ -49,15 +54,37 @@ Explained differently, Rougail allows you to easily implement an integration of
variable
family
tags
fill
Value checks <check>
condition
.. toctree::
:titlesonly:
:caption: Load values from user datas
user_datas/index
configuration
.. toctree::
:titlesonly:
:caption: The library
library/index
configuration
.. toctree::
:titlesonly:
:caption: Notes
developer
documentation
.. toctree::
:hidden:
install
naming_convention
.. rubric:: Index page

35
docs/install.rst Normal file
View file

@ -0,0 +1,35 @@
.. _installation:
Rougail library installation
======================================
Activate you virtual environment
------------------------------------
- Open a shell session
- install the virtual environment: `python -m'venv' .venv`
- activate it `./.venv/bin/activate` (or `.venv\Scripts\activate.exe` under windows)
Standard installation
---------------------------
You can use the `pip` python installer, here is the install command:
.. code-block:: bash
pip install rougail
Installation of third-party libraries
-------------------------------------------
First, download the :download:`requirements.txt file: <install/requirements.txt>`
.. literalinclude:: install/requirements.txt
:caption: The :file:`requirements.txt` requirements file
Then in your virtual environment, recursively install the third-party libraries as follows:
.. code-block:: bash
pip install -r requirements.txt --extra-index-url https://test.pypi.org/simple/

View file

@ -0,0 +1,16 @@
rougail==1.2.0a59
rougail-cli==0.2.0a42
rougail-output-ansible==0.2.0a24
rougail-output-display==0.2.0a29
rougail-output-doc==0.2.0a45
rougail-output-formatter==0.1.0a24
rougail-output-json==0.2.0a18
rougail-output-table==0.1.0a2
rougail-user-data-ansible==0.1.0a5
rougail-user-data-bitwarden==0.1.0a30
rougail-user-data-commandline==0.1.0a5
rougail-user-data-environment==0.1.0a18
rougail-user-data-questionary==0.1.0a4
rougail-user-data-yaml==0.2.0a19
tiramisu==5.2.0a25
tiramisu-cmdline-parser==0.7.0a5

View file

@ -1,233 +0,0 @@
`Rougail`'s library description
=================================
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 configuraiton options in :doc:`configuration`.
To load the configuration you must import the `RougailConfig` class and set the `dictionaries_dir` values:
.. code-block:: python
from rougail import RougailConfig
RougailConfig['dictionaries_dir'] = ['dict']
Let's convert a dictionary
-----------------------------
As a reminder, a :term:`dictionary` is a set of instructions that will allow us to create :term:`families` and :term:`variables`.
Let's start by creating a simple dictionary.
Here is a first :file:`dict/00-base.yml` dictionary:
.. code-block:: yaml
---
version: '1.1'
my_variable:
default: my_value
Then, let's create the :term:`Tiramisu` objects via the following script:
.. code-block:: python
:caption: the `script.py` file content
from rougail import Rougail, RougailConfig
RougailConfig['dictionaries_dir'] = ['dict']
rougail = Rougail()
config = rougail.get_config()
print(config.value.get())
Let's execute `script.py`:
.. code-block:: bash
$ python3 script.py
{'rougail.my_variable': 'my_value'}
Let's convert an extra dictionary
-------------------------------------
.. index:: extras
The default namespace for variables and families is `rougail`. It is possible to define other namespaces. These additional namespaces are called `extras`.
.. FIXME: faire une page pour les extras
Additional namespaces are defined during configuration.
For example, here's how to add an `example` namespace:
.. code-block:: python
RougailConfig['extra_dictionaries']['example'] = ['extras/']
Then let's create an extra :term:`dictionary` :file:`extras/00-base.yml`:
.. code-block:: yaml
:caption: the :file:`extras/00-base.yml` file content
---
version: '1.1'
my_variable_extra:
default: my_value_extra
Then, let's create the :term:`Tiramisu` objects via the following :file:`script.py` script:
.. code-block:: python
:caption: the :file:`script.py` file content
from rougail import Rougail, RougailConfig
RougailConfig['dictionaries_dir'] = ['dict']
RougailConfig['extra_dictionaries']['example'] = ['extras/']
rougail = Rougail()
config = rougail.get_config()
print(config.value.dict())
Let's execute `script.py`:
.. code-block:: bash
$ python3 script.py
{'rougail.my_variable': 'my_value', 'example.my_variable_extra': 'my_value_extra'}
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`:
.. code-block:: yaml
---
version: '1.1'
my_variable_jinja:
type: "string"
default:
type: jinja
jinja: "{{ return_no() }}"
Then let's define the :func:`return_no` function in :file:`functions.py`:
.. code-block:: python
:caption: the :file:`functions.py` content
def return_no():
return 'no'
Then, let's create the :term:`Tiramisu` objects via the following script:
.. code-block:: python
:caption: the `script.py` file content
from rougail import Rougail, RougailConfig
RougailConfig['dictionaries_dir'] = ['dict']
RougailConfig['extra_dictionaries']['example'] = ['extras/']
RougailConfig['functions_file'] = 'functions.py'
rougail = Rougail()
config = rougail.get_config()
print(config.value.dict())
Let's execute `script.py`:
.. code-block:: bash
$ python3 script.py
{'rougail.my_variable': 'my_value', 'rougail.my_variable_jinja': 'no', 'example.my_variable_extra': 'my_value_extra'}
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
----------------------
A variable has a type. This type enables the variable to define the values that are accepted by this variable.
There is a series of default types, but obviously not all cases are taken.
It's possible to create your own type.
Here an example to a lipogram option (in a string, we cannot use "e" character):
.. code-block:: python
:caption: the `lipogram.py` file content
from tiramisu import StrOption
class LipogramOption(StrOption):
__slots__ = tuple()
_type = 'lipogram'
def validate(self,
value):
super().validate(value)
# verify that there is any 'e' in the sentense
if 'e' in value:
raise ValueError('Perec wrote a book without any "e", you could not do it in a simple sentence?')
To add the new lipogram type in Rougail:
.. code-block:: python
>>> from rougail import Rougail, RougailConfig
>>> RougailConfig['dictionaries_dir'] = ['dict']
>>> RougailConfig['custom_types']['lipogram'] = LipogramOption
Now, we can use lipogram type.
Here is a :file:`dict/00-base.yml` dictionary:
.. code-block:: yaml
---
version: '1.1'
var:
type: lipogram
.. code-block:: python
>>> rougail = Rougail()
>>> config = rougail.get_config()
>>> 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')
[...]
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:`dictionary` 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:`dictionary` 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

View file

@ -0,0 +1,47 @@
Let's create a custom function
==============================
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
%YAML 1.2
---
version: 1.1
my_variable_jinja:
default:
type: jinja
jinja: "{{ return_no() }}"
...
Then let's define the :func:`return_no` function in :file:`functions.py`:
.. code-block:: python
:caption: the :file:`functions.py` content
def return_no():
return 'no'
Then, let's create the :term:`Tiramisu` objects via the following script:
.. code-block:: python
:caption: the `script.py` file content
from rougail import Rougail, RougailConfig
RougailConfig['main_structural_directories'] = ['dict']
RougailConfig['extra_namespaces']['example'] = ['extras/']
RougailConfig['functions_file'] = 'functions.py'
rougail = Rougail()
config = rougail.get_config()
print(config.value.dict())
Let's execute `script.py`:
.. code-block:: bash
$ python3 script.py
{'rougail.my_variable': 'my_value', 'rougail.my_variable_jinja': 'no', 'example.my_variable_extra': 'my_value_extra'}
The value of the `my_variable_extra` variable is calculated, and it's value comes from the :func:`return_no` function.

45
docs/library/extra.rst Normal file
View file

@ -0,0 +1,45 @@
Let's convert an extra namespace structural file
================================================
.. index:: extras
The default namespace for variables and families is `rougail`. It is possible to define other namespaces. These additional namespaces are called `extras`.
.. FIXME: faire une page pour les extras
Additional namespaces are defined during configuration.
For example, here's how to add an `example` namespace:
.. code-block:: python
RougailConfig['extra_namespaces']['example'] = ['extras/']
Then let's create an extra :term:`structure file` :file:`extras/00-base.yml`:
.. code-block:: yaml
:caption: the :file:`extras/00-base.yml` file content
---
version: '1.1'
my_variable_extra:
default: my_value_extra
Then, let's create the :term:`Tiramisu` objects via the following :file:`script.py` script:
.. code-block:: python
:caption: the :file:`script.py` file content
from rougail import Rougail, RougailConfig
RougailConfig['main_structural_directories'] = ['dict/']
RougailConfig['extra_namespaces']['example'] = ['extras/']
rougail = Rougail()
config = rougail.get_config()
print(config.value.dict())
Let's execute `script.py`:
.. code-block:: bash
$ python3 script.py
{'rougail.my_variable': 'my_value', 'example.my_variable_extra': 'my_value_extra'}

87
docs/library/index.rst Normal file
View file

@ -0,0 +1,87 @@
`Rougail`'s library description
=================================
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.
.. FIXME: You will find all the configuration options in doc:`configuration`
find a document with all the configuration options
To load the configuration you must import the `RougailConfig` class and set the `main_structural_directories` values:
.. code-block:: python
from rougail import RougailConfig
RougailConfig['main_structural_directories'] = ['dict']
Let's convert a our first structural file
-----------------------------------------
As a reminder, a :term:`structure file` is a set of instructions that will allow us to create :term:`families <family>` and :term:`variables <variable>`.
Let's start by creating a simple structure file.
Here is a first :file:`dict/00-base.yml` structure file:
.. code-block:: yaml
%YAML 1.2
---
version: 1.1
my_variable: my_value # my variable
...
Then, let's create the :term:`Tiramisu` objects via the following script:
.. code-block:: python
:caption: the `script.py` file content
from rougail import Rougail, RougailConfig
RougailConfig['main_structural_directories'] = ['dict']
rougail = Rougail()
config = rougail.run()
print(config.value.get())
.. demo:: Let's execute `script.py`:
.. code-block:: bash
$ python3 script.py
{'rougail.my_variable': 'my_value'}
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
.. toctree::
:titlesonly:
:caption: Use library
user_datas
output
parse
tags
rougailconfig_load_from_cli
extra
custom_function
own_type
upgrade

269
docs/library/output.rst Normal file
View file

@ -0,0 +1,269 @@
Display the result
==================
After construct a configuration, loads user datas, you can choose this configuration in different output format.
First of create, let's create a structural file like this:
.. code-block:: yaml
:caption: the :file:`dist/00-base.yml` file content
%YAML 1.2
---
version: 1.1
my_variable: my value # My first variable
my_boolean_variable: true # My boolean variable
my_integer_variable: 1 # My integer variable
my_secret_variable:
description: My secret variable
type: secret
default: MyVeryStrongPassword
...
Display in a console
--------------------
We can display configuration directly in the console:
.. code-block:: python
:caption: the :file:`script.py` file content
from rougail import Rougail, RougailConfig
from rougail.output_console import RougailOutputConsole
RougailConfig["main_namespace"] = None
RougailConfig["main_structural_directories"] = ["dist/"]
RougailConfig["step.output"] = "console"
rougail = Rougail()
config = rougail.run()
config.property.read_only()
RougailOutputConsole(config).print()
.. FIXME display console!
console.key_is_description
''''''''''''''''''''''''''
By default, the key is the variable description, if you prefer have only the path:
.. code-block:: python
:caption: the :file:`script.py` file content
from rougail import Rougail, RougailConfig
from rougail.output_json import RougailOutputJson
RougailConfig["main_namespace"] = None
RougailConfig["main_structural_directories"] = ["dist/"]
RougailConfig["step.output"] = "console"
rougail = Rougail()
config = rougail.run()
config.property.read_only()
RougailOutputJson(config).print()
.. FIXME display console!
console.show_secrets
''''''''''''''''''''
Secrets are remplace by "*******", to display real secrets:
.. code-block:: python
:caption: the :file:`script.py` file content
from rougail import Rougail, RougailConfig
from rougail.output_console import RougailOutputConsole
RougailConfig["main_namespace"] = None
RougailConfig["main_structural_directories"] = ["dist/"]
RougailConfig["step.output"] = "console"
RougailConfig["console.show_secrets"] = True
rougail = Rougail()
config = rougail.run()
config.property.read_only()
RougailOutputConsole(config).print()
.. FIXME display console!
console.mandatory
'''''''''''''''''
Before display configuration, mandatories variables are check. If you don't want, add the parameter `console.mandatory` to False:
.. code-block:: python
:caption: the :file:`script.py` file content
from rougail import Rougail, RougailConfig
from rougail.output_console import RougailOutputConsole
RougailConfig["main_namespace"] = None
RougailConfig["main_structural_directories"] = ["dist/"]
RougailConfig["step.output"] = "console"
RougailConfig["console.mandatory"] = False
rougail = Rougail()
config = rougail.run()
config.property.read_only()
RougailOutputConsole(config).print()
.. FIXME display console!
JSON
----
Your script can return a JSON object:
.. code-block:: python
:caption: the :file:`script.py` file content
from rougail import Rougail, RougailConfig
from rougail.output_console import RougailOutputConsole
RougailConfig["main_namespace"] = None
RougailConfig["main_structural_directories"] = ["dist/"]
RougailConfig["step.output"] = "json"
rougail = Rougail()
config = rougail.run()
config.property.read_only()
RougailOutputConsole(config).print()
Let's try this script:
.. code-block:: bash
$ python script.py
{
"my_variable": "my value",
"my_boolean_variable": true,
"my_integer_variable": 1,
"my_secret_variable": "MyVeryStrongPassword"
}
ANSIBLE
-------
It's possible to use Ansible has a output format.
The goal is here to use Ansible has a dynamic user's inventories structure manage by Rougail.
This output needs an extra namespace, named, by default, "hosts". This namespace define your hosts and groups.
Let's create a single group "my_group" with one host "group1.net":
.. code-block:: yaml
:caption: the :file:`hosts/00-hosts.yml` file content
%YAML 1.2
---
version: 1.1
hostnames:
my_group:
hosts:
type: domainname
default:
- group1.net
...
Now we can generate Ansible inventory:
.. code-block:: python
:caption: the :file:`script.py` file content
#!/bin/env python
from rougail import Rougail, RougailConfig
from rougail.output_ansible import RougailOutputAnsible
RougailConfig["main_namespace"] = "main"
RougailConfig["main_structural_directories"] = ["dist/"]
RougailConfig['extra_namespaces']['hosts'] = ['hosts/']
RougailConfig["step.output"] = "ansible"
rougail = Rougail()
config = rougail.run()
config.property.read_only()
RougailOutputAnsible(config).print()
We will retrieved all ours variables associate to this group with all variables inside the namespace `main`:
.. code-block:: bash
$ python script.py
{
"_meta": {
"hostvars": {
"group1.net": {
"ansible_host": "group1.net",
"main": {
"my_variable": "my value",
"my_boolean_variable": true,
"my_integer_variable": 1,
"my_secret_variable": "MyVeryStrongPassword"
}
}
}
},
"my_group": {
"hosts": [
"group1.net"
]
}
}
We can now use our script as an inventory source in Ansible:
.. code-block:: bash
$ chmod +x script.py
$ ansible-inventory -i script.py --list
{
"_meta": {
"hostvars": {
"group1.net": {
"ansible_host": "group1.net",
"main": {
"my_boolean_variable": true,
"my_integer_variable": 1,
"my_secret_variable": "MyVeryStrongPassword",
"my_variable": "my value"
}
}
}
},
"all": {
"children": [
"ungrouped",
"my_group"
]
},
"my_group": {
"hosts": [
"group1.net"
]
}
}
DOC
---
We can generate the documentation of all the Rougail variable:
.. code-block:: python
:caption: the :file:`script.py` file content
from rougail import Rougail, RougailConfig
from rougail.output_doc import RougailOutputDoc
RougailConfig["main_namespace"] = "main"
RougailConfig["main_structural_directories"] = ["dist/"]
RougailConfig["step.output"] = "doc"
rougail = Rougail()
config = rougail.run()
config.property.read_only()
RougailOutputDoc(config).print()
.. FIXME : display

52
docs/library/own_type.rst Normal file
View file

@ -0,0 +1,52 @@
Create your own type
====================
A variable has a type. This type enables the variable to define the values that are accepted by this variable.
There is a series of default types, but obviously not all cases are taken.
It's possible to create your own type.
Here an example to a lipogram option (in a string, we cannot use "e" character):
.. code-block:: python
:caption: the `lipogram.py` file content
from tiramisu import StrOption
class LipogramOption(StrOption):
__slots__ = tuple()
_type = 'lipogram'
def validate(self,
value):
super().validate(value)
# verify that there is any 'e' in the sentense
if 'e' in value:
raise ValueError('Perec wrote a book without any "e", you could not do it in a simple sentence?')
To add the new lipogram type in Rougail:
.. code-block:: python
>>> from rougail import Rougail, RougailConfig
>>> RougailConfig['main_structural_directories'] = ['dict']
>>> RougailConfig['custom_types']['lipogram'] = LipogramOption
Now, we can use lipogram type.
Here is a :file:`dict/00-base.yml` structure file:
.. code-block:: yaml
---
version: '1.1'
var:
type: lipogram
.. code-block:: python
>>> rougail = Rougail()
>>> config = rougail.get_config()
>>> 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')
[...]
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?

260
docs/library/parse.rst Normal file
View file

@ -0,0 +1,260 @@
Retrieve all variables and families
===================================
Rougail returns a :term:`Tiramisu` config.
Let's retrieve our variables and families to manager this.
First of all, create our structural file:
.. code-block:: yaml
:caption: the :file:`dist/00-base.yml` file content
%YAML 1.2
---
version: 1.1
my_variable: # a simple variable
- value1
- value2
a_family: # a simple family
my_variable: my_value # a simple variable inside the family
a_dyn_family_{{ identifier }}:
description: a dynamic family for "{{ identifier }}"
dynamic:
variable: _.my_variable
a_leadership:
description: a leader family
a_leader: # a leader variable
a_follower: # a follower variable
...
Walk through our config
-------------------------
Create our first script to walk through our config:
.. code-block:: python
:caption: the :file:`script.py` file content
from rougail import Rougail, RougailConfig
RougailConfig['main_structural_directories'] = ["dist/"]
rougail = Rougail()
config = rougail.run()
def walk(config):
for option in config:
print(option.description())
if option.isoptiondescription():
walk(option)
if __name__ == '__main__':
walk(config)
Let's execute `script.py`:
.. code-block:: bash
$ python3 script.py
rougail
rougail.my_variable (a simple variable)
rougail.a_family (a simple family)
rougail.a_family.my_variable (a simple variable inside the family)
rougail.a_dyn_family_value1 (a dynamic family for "value1")
rougail.a_dyn_family_value1.a_leadership (a leader family)
rougail.a_dyn_family_value1.a_leadership.a_leader (a leader variable)
rougail.a_dyn_family_value1.a_leadership.a_follower (a follower variable)
rougail.a_dyn_family_value2 (a dynamic family for "value2")
rougail.a_dyn_family_value2.a_leadership (a leader family)
rougail.a_dyn_family_value2.a_leadership.a_leader (a leader variable)
rougail.a_dyn_family_value2.a_leadership.a_follower (a follower variable)
We retrieve alls description of variables and families.
Let us distinguish the variables of the families
------------------------------------------------
.. code-block:: python
:caption: the :file:`script.py` file content
from rougail import Rougail, RougailConfig
RougailConfig['main_structural_directories'] = ["dist/"]
rougail = Rougail()
config = rougail.run()
def walk(config, level=0):
for option in config:
if option.isoptiondescription():
typ = "family"
else:
typ = "variable"
prefix = " " * level
print(f"{prefix}{typ}: {option.description()}")
if option.isoptiondescription():
walk(option, level + 1)
if __name__ == '__main__':
walk(config)
Let's execute `script.py`:
.. code-block:: bash
$ python3 script.py
family: rougail
variable: rougail.my_variable (a simple variable)
family: rougail.a_family (a simple family)
variable: rougail.a_family.my_variable (a simple variable inside the family)
family: rougail.a_dyn_family_value1 (a dynamic family for "value1")
family: rougail.a_dyn_family_value1.a_leadership (a leader family)
variable: rougail.a_dyn_family_value1.a_leadership.a_leader (a leader variable)
variable: rougail.a_dyn_family_value1.a_leadership.a_follower (a follower variable)
family: rougail.a_dyn_family_value2 (a dynamic family for "value2")
family: rougail.a_dyn_family_value2.a_leadership (a leader family)
variable: rougail.a_dyn_family_value2.a_leadership.a_leader (a leader variable)
variable: rougail.a_dyn_family_value2.a_leadership.a_follower (a follower variable)
Or if we want more precision:
.. code-block:: python
:caption: the :file:`script.py` file content
from rougail import Rougail, RougailConfig
RougailConfig['main_structural_directories'] = ["dist/"]
rougail = Rougail()
config = rougail.run()
def walk(config, level=0):
for option in config:
if option.isoptiondescription():
if option.isleadership():
typ = "leadership"
elif option.isdynamic():
typ = "dynamic family"
else:
typ = "family"
else:
if option.isleader():
typ = "leader"
elif option.isfollower():
typ = "follower"
else:
typ = "option"
if option.isdynamic():
typ = f"dynamic {typ}"
prefix = " " * level
print(f"{prefix}{typ}: {option.description()}")
if option.isoptiondescription():
walk(option, level + 1)
if __name__ == '__main__':
walk(config)
Let's execute `script.py`:
.. code-block:: bash
family: rougail
option: rougail.my_variable (a simple variable)
family: rougail.a_family (a simple family)
option: rougail.a_family.my_variable (a simple variable inside the family)
dynamic family: rougail.a_dyn_family_value1 (a dynamic family for "value1")
dynamic family: rougail.a_dyn_family_value1.a_leadership (a leader family)
dynamic option: rougail.a_dyn_family_value1.a_leadership.a_leader (a leader variable)
dynamic option: rougail.a_dyn_family_value1.a_leadership.a_follower (a follower variable)
dynamic family: rougail.a_dyn_family_value2 (a dynamic family for "value2")
dynamic family: rougail.a_dyn_family_value2.a_leadership (a leader family)
dynamic option: rougail.a_dyn_family_value2.a_leadership.a_leader (a leader variable)
dynamic option: rougail.a_dyn_family_value2.a_leadership.a_follower (a follower variable)
Get variable values
-------------------
If we want to walk to get variables and their values:
.. code-block:: python
:caption: the :file:`script.py` file content
from rougail import Rougail, RougailConfig
RougailConfig['main_structural_directories'] = ["dist/"]
rougail = Rougail()
config = rougail.run()
def walk(config):
for option in config:
if option.isoptiondescription():
walk(option)
else:
print(f"{option.description()}: {option.value.get()}")
if __name__ == '__main__':
walk(config)
Let's execute `script.py`:
.. code-block:: bash
rougail.my_variable (a simple variable): ['value1', 'value2']
rougail.a_family.my_variable (a simple variable inside the family): my_value
rougail.a_dyn_family_value1.a_leadership.a_leader (a leader variable): None
rougail.a_dyn_family_value1.a_leadership.a_follower (a follower variable): None
rougail.a_dyn_family_value2.a_leadership.a_leader (a leader variable): None
rougail.a_dyn_family_value2.a_leadership.a_follower (a follower variable): None
Modify variable values
----------------------
Some variables are mandatories but hasn't value. Here we set alls values:
.. code-block:: python
:caption: the :file:`script.py` file content
from rougail import Rougail, RougailConfig
RougailConfig['main_structural_directories'] = ["dist/"]
rougail = Rougail()
config = rougail.run()
def walk(config):
for option in config:
if option.isoptiondescription():
walk(option)
else:
print(f"{option.description()}: {option.value.get()}")
if __name__ == '__main__':
print("Mandatories variables without value:")
print(config.value.mandatory())
config.value.set("rougail.my_variable", ["value 5", "value 6"])
config.value.set("rougail.a_dyn_family_value_5.a_leadership.a_leader", "value 1")
config.value.set("rougail.a_dyn_family_value_5.a_leadership.a_follower", "value 2")
config.value.set("rougail.a_dyn_family_value_6.a_leadership.a_leader", "value 3")
config.value.set("rougail.a_dyn_family_value_6.a_leadership.a_follower", "value 4")
print("Mandatories variables without value:")
print(config.value.mandatory())
walk(config)
Let's execute `script.py`:
.. code-block:: bash
Mandatories variables without value:
[<TiramisuOption path="rougail.a_dyn_family_value1.a_leadership.a_leader">, <TiramisuOption path="rougail.a_dyn_family_value1.a_leadership.a_follower">, <TiramisuOption path="rougail.a_dyn_family_value2.a_leadership.a_leader">, <TiramisuOption path="rougail.a_dyn_family_value2.a_leadership.a_follower">]
Mandatories variables without value:
[]
rougail.my_variable (a simple variable): ['value 5', 'value 6']
rougail.a_family.my_variable (a simple variable inside the family): my_value
rougail.a_dyn_family_value_5.a_leadership.a_leader (a leader variable): value 1
rougail.a_dyn_family_value_5.a_leadership.a_follower (a follower variable): value 2
rougail.a_dyn_family_value_6.a_leadership.a_leader (a leader variable): value 3
rougail.a_dyn_family_value_6.a_leadership.a_follower (a follower variable): value 4

View file

@ -0,0 +1,167 @@
Load Rougail configuration from Rougail command line informations
==================================================================
There is a lot you can do with the Rougail command line (rougail-cli), but sometimes you need to do a more advanced script.
Rather than duplicating the configuration, why not load the information from the configuration file, environment variables, or command line options?
We can loading a combination of source information but always in this order:
- configuration file
- environment variables
- commandline options
.. warning:: specific options reserve for command line (in namespace "cli") are not available in script
Then let's create an structual file:term:`structure file` :file:`dist/00-base.yml`:
.. code-block:: yaml
:caption: the :file:`dist/00-base.yml` file content
\%YAML 1.2
---
version: 1.1
my_variable: my_value_extra # a simple variable
...
Command line configuration file
-------------------------------
Create a command line configuration file :file:`.rougailcli.yml`:
.. code-block:: yaml
:caption: the :file:`.rougailcli.yml` file content
---
main_structural_directories: # directories where are place structural file
- dist
step.output: json # output is not console but json
Let's execute Rougail command line:
.. code-block:: bash
$ rougail
{
"my_variable": "my_value_extra"
}
Then, let's create the :term:`Tiramisu` objects via the following :file:`script.py` script:
.. code-block:: python
:caption: the :file:`script.py` file content
from rougail import Rougail
rougail = Rougail()
try:
config = rougail.run()
print(config.value.get())
except Exception as err:
print(f"ERROR: {err}")
exit(1)
Let's execute `script.py`:
.. code-block:: bash
$ python3 script.py
ERROR: option "Directories where structural files are placed" is mandatory but has no value
As expected, the .rougailcli.yml file is not loaded because it is specific to the command line.
Let's modifying the script to do this:
.. code-block:: python
:caption: the :file:`script.py` file content
from rougail import Rougail, RougailConfig
from rougail.cli.rougailconfig import load
load(RougailConfig, yaml_file=".rougailcli.yml")
rougail = Rougail()
try:
config = rougail.run()
print(config.value.get())
except Exception as err:
print(f"ERROR: {err}")
exit(1)
Let's execute `script.py`:
.. code-block:: bash
$ python3 script.py
{<TiramisuOption path="rougail">: {<TiramisuOption path="rougail.my_variable">: 'my_value_extra'}}
Environment variables
---------------------
If we don't have .rougailcli.yml, it's possible to set option with environment variables, like this:
.. code-block:: bash
$ env ROUGAILCLI_MAIN_STRUCTURAL_DIRECTORIES=dist/ ROUGAILCLI_STEP.OUTPUT=json ROUGAILCLI_MAIN_NAMESPACE=test bin/rougail
{
"test": {
"my_variable": "my_value_extra"
}
}
Do the same with a script:
.. code-block:: python
:caption: the :file:`script.py` file content
from rougail import Rougail, RougailConfig
from rougail.cli.rougailconfig import load
load(RougailConfig, env_prefix="ROUGAILCLI")
rougail = Rougail()
try:
config = rougail.run()
print(config.value.get())
except Exception as err:
print(f"ERROR: {err}")
exit(1)
Let's execute `script.py`:
.. code-block:: bash
$ env ROUGAILCLI_MAIN_STRUCTURAL_DIRECTORIES=dist/ ROUGAILCLI_STEP.OUTPUT=json ROUGAILCLI_MAIN_NAMESPACE=test python3 script.py
{<TiramisuOption path="test">: {<TiramisuOption path="test.my_variable">: 'my_value_extra'}}
Command line option
-------------------
To reproduce this:
.. code-block:: bash
./bin/rougail --main_structural_directories dist/ --step.output json --main_namespace=new_test
{
"new_test": {
"my_variable": "my_value_extra"
}
}
Do this script:
.. code-block:: python
:caption: the :file:`script.py` file content
from rougail import Rougail, RougailConfig
from rougail.cli.rougailconfig import load
load(RougailConfig, commandline=True)
rougail = Rougail()
try:
config = rougail.run()
print(config.value.get())
except Exception as err:
print(f"ERROR: {err}")
exit(1)
.. code-block:: bash
$ python3 script.py --main_structural_directories dist/ --step.output json --main_namespace=new_test
{<TiramisuOption path="new_test">: {<TiramisuOption path="new_test.my_variable">: 'my_value_extra'}

112
docs/library/tags.rst Normal file
View file

@ -0,0 +1,112 @@
Use tag informations
====================
When we set tags for a variable, it will add :term:`Tiramisu` properties and informations.
We can filter those variables easily with tags.
First of all, create our structural file:
.. code-block:: yaml
:caption: the :file:`dist/00-base.yml` file content
%YAML 1.2
---
version: 1.1
infra_name: my infra # Name of this infrastructure
server1: # the first server
internal_domain:
description: Server domaine name
type: domainname
tags:
- internal
external_domain:
description: Domain name to access to this server for Internet
type: domainname
tags:
- external
server2: # the second server
address:
description: Server domaine name
type: domainname
tags:
- internal
...
Exclude variables with a specific tag
-------------------------------------
To exclude variables with a specific tag is very easy. When the variable has tags, properties with same name are automaticly create.
So exclude a tag means exclude variable with a particular property.
In this example we exclude variable with "internal" tag and display result of "server1" family:
.. code-block:: python
:caption: the :file:`script.py` file content
from rougail import Rougail, RougailConfig
from pprint import pprint
RougailConfig['main_namespace'] = None
RougailConfig['main_structural_directories'] = ['dist']
rougail = Rougail()
config = rougail.run()
print("without filter:")
pprint(config.option("server1").value.get())
config.property.add('internal')
print("with filter:")
pprint(config.option("server1").value.get())
Let's execute `script.py`:
.. code-block:: bash
$ python3 script.py
without filter:
{<TiramisuOption path="server1.external_domain">: None,
<TiramisuOption path="server1.internal_domain">: None}
with filter:
{<TiramisuOption path="server1.external_domain">: None}
Only variable with a specific tag
---------------------------------
It's more difficult to see only variable with a specific tag.
We have to walk through the configuration and retrieve variable with the selected tag.
Tags are in properties but, are in information too.
Here is a smal script that walk that the configuration and "print" option with "internal" tag:
.. code-block:: python
:caption: the :file:`script.py` file content
from rougail import Rougail, RougailConfig
RougailConfig['main_structural_directories'] = ['dist']
rougail = Rougail()
config = rougail.run()
def walk(config):
for option in config:
if option.isoptiondescription():
walk(option)
elif "internal" in option.information.get('tags', []):
print(option.description())
if __name__ == '__main__':
walk(config)
Let's execute `script.py`:
.. code-block:: bash
$ python3 script.py
server1.internal_domain (Server domaine name)
server2.address (Server domaine name)

42
docs/library/upgrade.rst Normal file
View file

@ -0,0 +1,42 @@
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.0'
my_variable:
multi: true
my_dyn_family:
type: "dynamic"
variable: my_variable
a_variable:
.. code-block:: python
>>> from rougail import RougailUpgrade, RougailConfig
>>> RougailConfig['main_structural_directories'] = ['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

390
docs/library/user_datas.rst Normal file
View file

@ -0,0 +1,390 @@
Load user datas
===============
User datas are values setup by user for configuration variables.
There is differents types of user datas for differents sources types.
We can cumulate user datas loader.
For this section, we will use :file:`dict/00-base.yml` a structure file:
.. code-block:: yaml
%YAML 1.2
---
version: 1.1
my_variable: my value # My first variable
my_boolean_variable: true # My boolean variable
my_integer_variable: 1 # My integer variable
my_secret_variable:
description: My secret variable
type: secret
my_hidden_variable:
description: My hidden variable
hidden: true
...
Here is the first script which is load this file:
.. code-block:: python
:caption: the :file:`script.py` file content
from rougail import Rougail, RougailConfig
RougailConfig["main_namespace"] = None
RougailConfig["main_structural_directories"] = ["dist/"]
rougail = Rougail()
config = rougail.run()
print(config.value.get())
Let's execute `script.py`:
.. code-block:: bash
$ python3 script.py
{<TiramisuOption path="my_variable">: 'my value', <TiramisuOption path="my_boolean_variable">: True, <TiramisuOption path="my_integer_variable">: 1, <TiramisuOption path="my_secret_variable">: None}
YAML
----
We want to load this YAML file with value define by user:
.. code-block:: yaml
---
my_variable: a new value
my_boolean_variable: false
my_integer_variable: 10
my_secret_variable: MyVeryStrongPassword
Here is the script which is load user data from the YAML file:
.. code-block:: python
:caption: the :file:`script.py` file content
from rougail import Rougail, RougailConfig
from rougail.user_data_yaml import RougailUserDataYaml
RougailConfig["main_namespace"] = None
RougailConfig["main_structural_directories"] = ["dist/"]
RougailConfig["step.user_data"] = ["yaml"]
RougailConfig["yaml.filename"] = ["dist.yml"]
rougail = Rougail()
config = rougail.run()
user_datas = RougailUserDataYaml(config).run()
rougail.user_datas(user_datas)
print(config.value.get())
Let's execute `script.py`:
.. code-block:: bash
$ python3 script.py
{<TiramisuOption path="my_variable">: 'a new value', <TiramisuOption path="my_boolean_variable">: False, <TiramisuOption path="my_integer_variable">: 10, <TiramisuOption path="my_secret_variable">: 'MyVeryStrongPassword'}
Set a secret in clear text file is not always a good idea.
This is why the `yaml.file_with_secrets` parameter allows you to define whether files define in `yaml.filename` can contain a secret and which one:
- all: all file can contains secret
- first: only the first file can contains secret
- last: only the last file can contains secret
- none: no file can contains secret
.. code-block:: python
:caption: the :file:`script.py` file content
from rougail import Rougail, RougailConfig
from rougail.user_data_yaml import RougailUserDataYaml
RougailConfig["main_namespace"] = None
RougailConfig["main_structural_directories"] = ["dist/"]
RougailConfig["step.user_data"] = ["yaml"]
RougailConfig["yaml.filename"] = ["dist.yml"]
RougailConfig["yaml.file_with_secrets"] = "none"
rougail = Rougail()
config = rougail.run()
user_datas = RougailUserDataYaml(
config,
).run()
rougail.user_datas(user_datas)
print(config.value.get())
Let's execute `script.py`:
.. code-block:: bash
$ python3 script.py
{<TiramisuOption path="my_variable">: 'a new value', <TiramisuOption path="my_boolean_variable">: False, <TiramisuOption path="my_integer_variable">: 10, <TiramisuOption path="my_secret_variable">: None}
Environment variables
---------------------
We can define use data from environment variables. The environment name is a "prefix" (ROUGAIL by default) with "_" and variable name in uppercase format.
For example:
- `my_variable` has `ROUGAIL_MY_VARIABLE` as a environment variable name
- `my_family.my_variable` has `ROUGAIL_MY_FAMILY.MY_VARIABLE` as a environment variable name
Some shell doesn't allow dot in environment file. In this case use the command "env".
For example: `env ROUGAIL_MY_FAMILY.MY_VARIABLE="value" ./script.py`.
Here is the script:
.. code-block:: python
:caption: the :file:`script.py` file content
from rougail import Rougail, RougailConfig
from rougail.user_data_environment import RougailUserDataEnvironment
RougailConfig["main_namespace"] = None
RougailConfig["main_structural_directories"] = ["dist/"]
RougailConfig["step.user_data"] = ["environment"]
rougail = Rougail()
config = rougail.run()
user_datas = RougailUserDataEnvironment(config).run()
rougail.user_datas(user_datas)
print(config.value.get())
Let's execute `script.py`:
.. code-block:: bash
env ROUGAIL_MY_VARIABLE="a new value" ROUGAIL_MY_BOOLEAN_VARIABLE="False" ROUGAIL_MY_INTEGER_VARIABLE=10 ROUGAIL_MY_SECRET_VARIABLE="MyVeryStrongPassword" python script.py
{<TiramisuOption path="my_variable">: 'a new value', <TiramisuOption path="my_boolean_variable">: False, <TiramisuOption path="my_integer_variable">: 10, <TiramisuOption path="my_secret_variable">: 'MyVeryStrongPassword'}
We can redefine the prefix with `environment.default_environment_name` (prefix is always uppercase characters):
.. code-block:: python
:caption: the :file:`script.py` file content
from rougail import Rougail, RougailConfig
from rougail.user_data_environment import RougailUserDataEnvironment
RougailConfig["main_namespace"] = None
RougailConfig["main_structural_directories"] = ["dist/"]
RougailConfig["step.user_data"] = ["environment"]
RougailConfig["environment.default_environment_name"] = "EX"
rougail = Rougail()
config = rougail.run()
user_datas = RougailUserDataEnvironment(config).run()
rougail.user_datas(user_datas)
print(config.value.get())
Let's execute `script.py`:
.. code-block:: bash
env EX_MY_VARIABLE="a new value" EX_MY_BOOLEAN_VARIABLE="False" EX_MY_INTEGER_VARIABLE=10 EX_MY_SECRET_VARIABLE="MyVeryStrongPassword" python script.py
{<TiramisuOption path="my_variable">: 'a new value', <TiramisuOption path="my_boolean_variable">: False, <TiramisuOption path="my_integer_variable">: 10, <TiramisuOption path="my_secret_variable">: 'MyVeryStrongPassword'}
If you define a `main_namespace` or `extra_namespaces`, the `environment.default_environment_name` is automaticly define with the name of the namespace in uppercase. And the separator is no more "_" but ".":
.. code-block:: python
:caption: the :file:`script.py` file content
from rougail import Rougail, RougailConfig
from rougail.user_data_environment import RougailUserDataEnvironment
RougailConfig["main_namespace"] = "main"
RougailConfig["main_structural_directories"] = ["dist/"]
RougailConfig["step.user_data"] = ["environment"]
rougail = Rougail()
config = rougail.run()
user_datas = RougailUserDataEnvironment(config).run()
rougail.user_datas(user_datas)
print(config.value.get())
Let's execute `script.py`:
.. code-block:: bash
env MAIN.MY_VARIABLE="a new value" MAIN.MY_BOOLEAN_VARIABLE="False" MAIN.MY_INTEGER_VARIABLE=10 MAIN.MY_SECRET_VARIABLE="MyVeryStrongPassword" python script.py
{<TiramisuOption path="main">: {<TiramisuOption path="main.my_variable">: 'a new value', <TiramisuOption path="main.my_boolean_variable">: False, <TiramisuOption path="main.my_integer_variable">: 10, <TiramisuOption path="main.my_secret_variable">: 'MyVeryStrongPassword'}}
Set a secret in clear variable environment is not always a good idea.
This is why the `environment.with_secrets` parameter allows you to reject secret from environment variable:
.. code-block:: python
:caption: the :file:`script.py` file content
from rougail import Rougail, RougailConfig
from rougail.user_data_environment import RougailUserDataEnvironment
RougailConfig["main_namespace"] = None
RougailConfig["main_structural_directories"] = ["dist/"]
RougailConfig["step.user_data"] = ["environment"]
RougailConfig["environment.with_secrets"] = False
rougail = Rougail()
config = rougail.run()
user_datas = RougailUserDataEnvironment(config).run()
rougail.user_datas(user_datas)
print(config.value.get())
Let's execute `script.py`:
.. code-block:: bash
env ROUGAIL_MY_VARIABLE="a new value" ROUGAIL_MY_BOOLEAN_VARIABLE="False" ROUGAIL_MY_INTEGER_VARIABLE=10 ROUGAIL_MY_SECRET_VARIABLE="MyVeryStrongPassword" python script.py
{<TiramisuOption path="my_variable">: 'a new value', <TiramisuOption path="my_boolean_variable">: False, <TiramisuOption path="my_integer_variable">: 10, <TiramisuOption path="my_secret_variable">: None}
Comand line parser user data
----------------------------
Value can be define directly with command line arguments:
.. code-block:: python
:caption: the :file:`script.py` file content
from rougail import Rougail, RougailConfig
from rougail.user_data_commandline import RougailUserDataCommandline
RougailConfig["main_namespace"] = None
RougailConfig["main_structural_directories"] = ["dist/"]
RougailConfig["step.user_data"] = ["commandline"]
rougail = Rougail()
config = rougail.run()
user_datas = RougailUserDataCommandline(
config,
).run()
rougail.user_datas(user_datas)
print(config.value.get())
Let's execute `script.py` to display help:
.. code-block:: bash
$ python script.py -h
usage: script.py [-h] --my_variable [MY_VARIABLE] --my_boolean_variable --no-my_boolean_variable --my_integer_variable [MY_INTEGER_VARIABLE] --my_secret_variable MY_SECRET_VARIABLE
options:
-h, --help show this help message and exit
--my_variable [MY_VARIABLE]
my_variable (My first variable) (default: my value)
--my_boolean_variable
my_boolean_variable (My boolean variable) (default: True)
--no-my_boolean_variable
--my_integer_variable [MY_INTEGER_VARIABLE]
my_integer_variable (My integer variable) (default: 1)
--my_secret_variable MY_SECRET_VARIABLE
my_secret_variable (My secret variable)
{<TiramisuOption path="my_variable">: 'my value', <TiramisuOption path="my_boolean_variable">: True, <TiramisuOption path="my_integer_variable">: 1, <TiramisuOption path="my_secret_variable">: None}
And now with modified value:
.. code-block:: bash
$ python script.py --my_variable "a new value" --no-my_boolean_variable --my_integer_variable 10 --my_secret_variable MyVeryStrongPassword
{<TiramisuOption path="my_variable">: 'a new value', <TiramisuOption path="my_boolean_variable">: False, <TiramisuOption path="my_integer_variable">: 10, <TiramisuOption path="my_secret_variable">: 'MyVeryStrongPassword'}
Boolean variable has a special behavour. To set False you need to add --no-VARIABLE, to set True you need to add --VARIABLE parameter.
.. ansible,bitwarden,questionary
Combine user datas
------------------
You can combine user datas, for example if you want to load datas from environment and/or command line argument:
.. code-block:: python
:caption: the :file:`script.py` file content
from rougail import Rougail, RougailConfig
from rougail.user_data_environment import RougailUserDataEnvironment
from rougail.user_data_commandline import RougailUserDataCommandline
RougailConfig["main_namespace"] = None
RougailConfig["main_structural_directories"] = ["dist/"]
RougailConfig["step.user_data"] = ["environment", "commandline"]
rougail = Rougail()
config = rougail.run()
user_datas = []
user_datas.extend(RougailUserDataEnvironment(
config,
).run())
user_datas.extend(RougailUserDataCommandline(
config,
).run())
rougail.user_datas(user_datas)
print(config.value.get())
Let's execute `script.py` with environment variable and commandline arguments:
.. code-block:: bash
$ env ROUGAIL_MY_VARIABLE="a new value" ROUGAIL_MY_BOOLEAN_VARIABLE="False" python script.py --my_integer_variable 10 --my_secret_variable MyVeryStrongPassword
{<TiramisuOption path="my_variable">: 'a new value', <TiramisuOption path="my_boolean_variable">: False, <TiramisuOption path="my_integer_variable">: 10, <TiramisuOption path="my_secret_variable">: 'MyVeryStrongPassword'}
If the value of a variable is define with an environment variable and commandline argument, the value is the value of the last user data define:
.. code-block:: bash
$ env ROUGAIL_MY_VARIABLE="not a new" python script.py --my_variable "a new value" --no-my_boolean_variable --my_integer_variable 10 --my_secret_variable MyVeryStrongPassword
{<TiramisuOption path="my_variable">: 'a new value', <TiramisuOption path="my_boolean_variable">: False, <TiramisuOption path="my_integer_variable">: 10, <TiramisuOption path="my_secret_variable">: 'MyVeryStrongPassword'}
Manage errors and warnings
--------------------------
Recreate a script with environnement variable support which is display the return of user_datas function:
.. code-block:: python
:caption: the :file:`script.py` file content
from rougail import Rougail, RougailConfig
from rougail.user_data_environment import RougailUserDataEnvironment
RougailConfig["main_namespace"] = None
RougailConfig["main_structural_directories"] = ["dist/"]
RougailConfig["step.user_data"] = ["environment"]
RougailConfig["environment.with_secrets"] = False
rougail = Rougail()
config = rougail.run()
user_datas = RougailUserDataEnvironment(
config,
).run()
print(rougail.user_datas(user_datas))
Try to load the value an unknown variable:
.. code-block:: bash
$ env ROUGAIL_UNKNOWN_VARIABLE="a value" python script.py
{'errors': [], 'warnings': ['variable or family "unknown_variable" does not exist, it will be ignored when loading from environment variable']}
As you can see, a warnings is return.
Try to load the value of an hidden variable:
.. code-block:: bash
$ env ROUGAIL_MY_HIDDEN_VARIABLE="a value" python script.py
{'errors': [], 'warnings': ['variable "my_hidden_variable" (My hidden variable) is hidden, it will be ignored when loading from environment variable']}
Finally if a try to change the value of a secret, which is not allowed:
.. code-block:: bash
$ env ROUGAIL_MY_SECRET_VARIABLE="MyVeryStrongPassword" python script.py
{'errors': ['the variable "my_secret_variable" contains secrets and should not be defined in environment variable'], 'warnings': []}
An error is generated.

View file

@ -0,0 +1,46 @@
.. _namingconvention:
File naming convention
==========================
The structure files in a given folder
---------------------------------------
For the sake of clarity, we put the structure definitions in separate files.
It's a good way to organize your rougail structures this way,
in the real world we need separate files for different topics.
For example some files like this:
A file named :file:`firefox/00-proxy.yml` structure file and file named :file:`firefox/10-manual.yml`
::
.
└── firefox
├── 00-proxy.yml
└── 10-manual.yml
.. note:: We of course could have put everything in one file.
Again, it is better to separate the structures in different files
for reasons of ordering and clarity.
Ordering your structure files
--------------------------------
The order in which files are loaded is important in Rougail.
In a given folder, files are loaded in alphabetical order.
Furthermore, for better readability of the order in which files are
loaded into a folder, we have adopted a convention.
To facilitate classification, we have defined a standard notation for structure file names:
::
XX-<name>.yml
Where `XX` is a two digits integer followed by an hyphen, and `<name>` is a name that describes
the structure that is in this file. We advise you to adopt this convention as well.
Moreover, it is preferable to only use lowercase ASCII letters, numbers and the `"_"` (undescore) character.
The snake case typographic convention is therefore used.

25
docs/readme.txt Normal file
View 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)>`_

50
docs/relative_path.rst Normal file
View file

@ -0,0 +1,50 @@
:orphan:
on a un path relatif, `_` ou `__`
https://forge.cloud.silique.fr/stove/rougail-tutorials/compare/v1.1_091~1..v1.1_091
::
manual:
use_for_https: true # Also use this proxy for HTTPS
"{{ identifier }}_proxy":
description: "{{ identifier }} Proxy"
dynamic:
- HTTPS
- SOCKS
hidden:
jinja: |
{% if my_identifier == 'HTTPS' and _.use_for_https %}
HTTPS is same has HTTP
{% endif %}
params:
my_identifier:
type: identifier
description: |
in HTTPS case if "manual.use_for_https" is set to True
address:
description: "{{ identifier }} address"
default:
variable: __.http_proxy.address
port:
description: "{{ identifier }} port"
default:
variable: __.http_proxy.port
version:
description: SOCKS host version used by proxy
choices:
- v4
- v5
default: v5
disabled:
type: identifier
when: 'HTTPS'

77
docs/rw_ro_modes.rst Normal file
View file

@ -0,0 +1,77 @@
:orphan:
Read-write or read-only modes
==================================
The read-write mode
--------------------
When you are in the design phase, your are designing the structure file or
setting values is some user data files, you have the role of :term:`integrator`
or :term:`operator`. Then you need to have access to all the datas, even
those which are :term:`hidden` or :term:`disabled`\ .
In this phase, the configuration shall be in `RW` mode.
.. glossary::
read-write
In the read-write mode (RW mode), all settings are accessible and can
be modified. For example, a variable which has the `frozen` property
can be modified. A `hidden` or `disabled` variable is accessible.
We are in the rôle of an :term:`integrator` or an :term:`operator`.
We are therefore in a situation where we are **using** this configuration.
The read-only mode
--------------------
Once the configuration has beed designed, it is used.
The situation is different. The configuration behaves as a system.
.. glossary::
read-only
In the read-only mode (RO mode), the configuration cannot be modified.
We are **using** a configuration.
In the usage mode, we are therefore in a situation where the configuration
cannot be changed. The configuration's data are immutable.
RO or RW mode?
---------------
Here is an image which summarizes these explanations:
.. image:: images/read_write.png
How to enable
By default in `rougail-cli`, the `RO` mode is activated.
If you need to enable the `RW` mode, there is an `rougail-cli` option:
.. code-block:: bash
env ROUGAIL_MANUAL.USE_FOR_HTTPS=true rougail -m structfile/proxy2.yml -u yaml environment --yaml.filename userdata/proxy.yml -o json --json.read_write
The output is:
.. code-block:: json
{
"manual": {
"http_proxy": {
"address": "toto.fr",
"port": "8888"
},
"use_for_https": true
}
}

101
docs/structfile.rst Normal file
View file

@ -0,0 +1,101 @@
The structure files
=====================
Definition
------------
.. glossary::
structure file
A structure file in the Rougail meaning is a YAML file that describes variables
and their dependencies.
There can be a lot of structure files located in many different folders.
Rougail reads all the structure files and loads them into a single object
that represents the whole :term:`context`.
The header of a structure file
-----------------------------------
A basic structure file is composed of:
- a YAML 1.2 standard header
- a version attribute
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_000/firefox/00-proxy.yml
:language: yaml
:caption: An empty Rougail structure file.
:name: RougailStructureFirstVariableDescription
..
%YAML 1.2
---
version: 1.1
...
The structure file processing
----------------------------------
The structured files contain information that will be used by the consistency
handling system for structure validation.
.. figure:: images/schema.png
:alt: The Rougail process
:align: center
The Rougail process from structure files to Tiramisu valition handler object
The structured data
---------------------
.. glossary::
structured data
We sometimes call "structured datas" the datas that are defined in the structure files,
as opposed to :term:`user datas <user data>`\ .
For example when a value of a variable is defined in the structured datas, that is
in a structured file, the assigned value's status is that the variable's value is a default value.
If the assigned value of the variable is defined in a user data file,
it is an user assigned value.
We'll see later on some examples of default values and user assigned values.
The main advantage of all of this that declaring variables and writing consistency is as simple
as writing YAML. With Rougail it is not necessary to write :term:`Tiramisu` code any more.
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 style (that is, in a YAML file).
Once the structure files are loaded by Rougail, the Tiramisu configuration management tool can check the consistency of the variables between them.
What contains exactly a :term:`structure file`?
-------------------------------------------------
A :term:`structure file` is a YAML file whose structure is described in this documentation page.
A structure file contains a set of variables loaded into :term:`Tiramisu`, usable in your application, for example in a template
to generate configuration files.
:term:`Families <family>` and :term:`variables <variable>` can be defined in several structure files. These structure files are then aggregated.
If you want to see the contents of a structure file, you can have a look at the :ref:`tutorial with a real world sample. <tutorial>`
The default namespace
-------------------------
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.
The extra structure files
---------------------------
An extra is a different namespace. The idea is to be able to classify the variables by theme.
Extra namespaces must be declared :doc:`when configuring Rougail <configuration>`.
In this namespace we cannot access variables from another `extra` namespace.
On the other hand, it is possible to access the variable of the default namespace.

34
docs/tags.rst Normal file
View file

@ -0,0 +1,34 @@
The variable tags
==================
A tag allows you to specialize a variable.
A tag can have several functions:
- Additional information in the documentation
- Variable exclusion
- Variable selection
- ...
Tags are only available for variable.
Here is an example:
.. code-block:: yaml
%YAML 1.2
---
version: 1.1
internal_domain:
description: Server domaine name
type: domainname
tags:
- internal
external_domain:
description: Domain name to access to this server for Internet
type: domainname
tags:
- external
...

View file

@ -0,0 +1,259 @@
Calculated default value for a variable
============================================
.. objectives:: Objectives
In this section we will reuse the value of a variable for the default value of another variable.
We will first build the `https_proxy` family which will be used to illustrate this functionality.
.. prerequisites:: Prerequisites
- We assume that Rougail's library is :ref:`installed <installation>` on your computer.
- It is possible to retrieve the current state of the various Rougail files manipulated in this tutorial step
by checking out the corresponding tag of the `rougail-tutorials` git repository.
Each tag corresponds to a stage of progress in the tutorial.
Of course, you can also decide to copy/paste or download the tutorial files contents while following the tutorial steps.
If you want to follow this tutorial with the help of the corresponding :tutorial:`rougail-tutorials git repository <src/branch/1.1>`,
this workshop page corresponds to the tags :tutorial:`v1.1_040 <src/tag/v1.1_040>` to :tutorial:`v1.1_041 <src/tag/v1.1_041>`
in the repository.
::
git clone https://forge.cloud.silique.fr/stove/rougail-tutorials.git
git switch --detach v1.1_040
HTTPS family
-------------
We have split the definition of the `manual` family into two specific files,
the :file:`10-manual.yml` and the :file:`20-manual.yml` structure file:
.. raw:: html
:class: output
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_042/tree.html
We will continue to complete the :file:`20-manual.yml` structure file,
with variables entirely equivalent to those found in the `http_proxy` family from the :file:`10-manual.yml` structure file.
Until now, we only had the `use_for_https` :ref:`boolean variable in this structure file <boolean_variable>`.
Now we are going to define an address and a port in the `https_proxy` family:
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/commit/v1.1_040/firefox/20-manual.yml
:linenos:
:language: yaml
:caption: The updated `manual` family in the :file:`firefox/20-manual.yml` structure file with the `https_proxy` family.
..
%YAML 1.2
---
version: 1.1
manual:
use_for_https: true # Also use this proxy for HTTPS
https_proxy: # HTTPS Proxy
address:
description: HTTPS address
type: domainname
params:
allow_ip: true
port:
description: HTTPS Port
type: port
default: 8080
A default value calculated from another variable
--------------------------------------------------
.. type-along:: For those who follow the tutorial with the help of the git repository
Now you need to checkout the `v1.1_041` version::
git switch --detach v1.1_041
.. discussion:: A contextualized default value
A contextualized default value is a default value that changes according to the overall context of the configuration.
A contextualized default value can come from, as we see here, a copy of the value of another variable.
The dynamic setting of a default value can be expressed this way: the default value is a pointer to another variable's value.
Here, the defaut value of `manual.https_proxy.address` points to the value of `manual.http_proxy.address`.
This is the same thing for the default value of the `manual.https_proxy.port` variable,
which points to the `manual.http_proxy.port` value.
.. note:: In the following we will see that the `manual.https_proxy.address` type is `domainname`.
Indeed, this `domainname` type comes from the type of the variable it points to.
Reminder: here is the HTTP :file:`firefox/10-manual.yml` structure file:
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/commit/v1.1_041/firefox/10-manual.yml
:linenos:
:language: yaml
:caption: The :file:`firefox/10-manual.yml` structure file with the `http_proxy` family and the `disabled` property
..
%YAML 1.2
---
version: 1.1
manual: # Manual proxy configuration
http_proxy: # HTTP Proxy
address:
description: HTTP address
type: domainname
params:
allow_ip: true
port:
description: HTTP Port
type: port
default: 8080
...
And here is the :file:`firefox/20-manual.yml` structure file where the calculated default values are:
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/commit/v1.1_041/firefox/20-manual.yml
:linenos:
:language: yaml
:caption: The :file:`firefox/20-manual.yml` structure file with the `hidden` property on the `https_proxy` family.
..
%YAML 1.2
---
version: 1.1
manual:
use_for_https: true # Also use this proxy for HTTPS
https_proxy: # HTTPS Proxy
address:
description: HTTPS address
default:
variable: __.http_proxy.address
port:
description: HTTPS Port
default:
variable: __.http_proxy.port
...
We can see here that the `address` variable's default value is conditionned by the `__.http_proxy.address` variable's value.
The target variable is `manual.http_proxy.address`.
.. note:: The `__.` notation means the parent path of the current subfamily path.
In the python quasi algorithmic notation we could say that:
.. code-block:: python
__.http_proxy.address == http_proxy.address
This is true only because in our use case `http_proxy.address` is located
in the same `manual` subfamiliy than `https_proxy.address`.
We then say that the `manual.https_proxy.address` and the `manual.https_proxy.port` default values are *calculated*.
.. glossary::
calculated
We say that a variable's value or a default variable's value is calculated
when there is a pointer which refers to another variable's value.
:ref:`Other types of calculations exists <calculated_variable>`, in which this type of behavior does not occur
(the "pointer" behavior, notably type copying).
The other types of calculation we will be explained later in this tutorial.
We're going to load some :term:`user data <user data>` to see what happens:
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/commit/v1.1_041/config/01/config.yml
:linenos:
:language: yaml
:caption: The :file:`config/01/config.yml` user data
..
---
proxy_mode: Manual proxy configuration
manual:
http_proxy:
address: http.proxy.net
port: 3128
use_for_https: false
https_proxy:
address: https.proxy.net
Let's launch the Rougail CLI on it:
.. raw:: html
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/commit/v1.1_041/config/01/cmd_ro.txt
:class: terminal
..
rougail -m firefox/ -u yaml -yf config/01/config.yml
We have this result:
.. raw:: html
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/commit/v1.1_041/config/01/output_ro.html
:class: output
..
<pre>╭────────────── Caption ───────────────╮
│ Variable <span style="color: #ffd700">Default value</span> │
│ <span style="color: #00aa00">Modified value</span> │
│ (⏳ Original default value) │
╰──────────────────────────────────────╯
Variables:
<span style="color: #5c5cff">┣━━ </span>📓 proxy_mode (Configure Proxy Access to the Internet): <span style="color: #00aa00">Manual proxy </span>
<span style="color: #5c5cff">┃ </span><span style="color: #00aa00">configuration</span> ◀ loaded from the YAML file "config/01/config.yml" (⏳ No
<span style="color: #5c5cff">┃ </span>proxy)
<span style="color: #5c5cff">┗━━ </span>📂 manual (Manual proxy configuration)
<span style="color: #5c5cff"> </span><span style="color: #5c5cff">┣━━ </span>📂 http_proxy (HTTP Proxy)
<span style="color: #5c5cff"> </span><span style="color: #5c5cff">┃ </span><span style="color: #5c5cff">┣━━ </span>📓 address (HTTP address): <span style="color: #00aa00">http.proxy.net</span> ◀ loaded from the YAML
<span style="color: #5c5cff"> </span><span style="color: #5c5cff">┃ </span><span style="color: #5c5cff">┃ </span>file "config/01/config.yml"
<span style="color: #5c5cff"> </span><span style="color: #5c5cff">┃ </span><span style="color: #5c5cff">┗━━ </span>📓 port (HTTP Port): <span style="color: #00aa00">3128</span> ◀ loaded from the YAML file
<span style="color: #5c5cff"> </span><span style="color: #5c5cff">┃ </span><span style="color: #5c5cff"> </span>"config/01/config.yml" (⏳ 8080)
<span style="color: #5c5cff"> </span><span style="color: #5c5cff">┣━━ </span>📓 use_for_https (Also use this proxy for HTTPS): <span style="color: #00aa00">false</span> ◀ loaded from
<span style="color: #5c5cff"> </span><span style="color: #5c5cff">┃ </span>the YAML file "config/01/config.yml" (⏳ true)
<span style="color: #5c5cff"> </span><span style="color: #5c5cff">┗━━ </span>📂 https_proxy (HTTPS Proxy)
<span style="color: #5c5cff"> </span><span style="color: #5c5cff"> </span><span style="color: #5c5cff">┣━━ </span>📓 address (HTTPS address): <span style="color: #00aa00">https.proxy.net</span> ◀ loaded from the YAML
<span style="color: #5c5cff"> </span><span style="color: #5c5cff"> </span><span style="color: #5c5cff">┃ </span>file "config/01/config.yml" (⏳ http.proxy.net)
<span style="color: #5c5cff"> </span><span style="color: #5c5cff"> </span><span style="color: #5c5cff">┗━━ </span>📓 port (HTTPS Port): <span style="color: #ffd700">3128</span>
</pre>
Notice that the default value is not changed when some values are settled from the :term:`user data file <user data>`.
The `https_proxy` variable's value is indeed modified, but the default value is still a calculated value (so the default value is indeed the value of the variable `http_proxy.address`, i.e., `"http.proxy.net"`), while the value defined by the user is `https.proxy.net`.
With the standard output of the Rougail CLI, the terminal display output, we can see the default value of the variable.
By interpreting the results of this standard output, we can see that even if a value has been assigned to this variable
(meaning the default value is not used) the variable's default value is not changed, but rather its actual value.
.. keypoints:: Key points progress
**summary**
We have learned how to set the default value of the `https_proxy.address` variable
with the value of the `http_proxy.address` variable. We did the same
`https_proxy.port` and the `https_proxy.port` variables.
**notation**
We have learned a notation very usefull for the subfamilies traversal:
- the `_.` notation means the current path of the family you're currently in
- the `__.` notation means the parent path of the current subfamily path.

222
docs/tutorial/choice.rst Normal file
View file

@ -0,0 +1,222 @@
A variable with possible values
==================================
.. objectives:: Objectives
We will learn how to define variables with predefined available values.
.. prerequisites:: Prerequisites
- We assume that Rougail's library is :ref:`installed <installation>` on your computer.
- It is possible to retrieve the current state of the various Rougail files manipulated in this tutorial step
by checking out the corresponding tag of the `rougail-tutorials` git repository.
Each tag corresponds to a stage of progress in the tutorial.
Of course, you can also decide to copy/paste or download the tutorial files contents while following the tutorial steps.
If you want to follow this tutorial with the help of the corresponding :tutorial:`rougail-tutorials git repository <src/branch/1.1>`,
this workshop page corresponds to the tag :tutorial:`v1.1_010 <src/tag/v1.1_010>` in the repository:
::
git clone https://forge.cloud.silique.fr/stove/rougail-tutorials.git
git switch --detach v1.1_010
A variable with a list of possible values
---------------------------------------------
In the firefox browser, the proxy mode can be set by this way:
.. image:: images/firefox_02.png
A list of possible values for the `proxy_mode` variable is proposed.
With Rougail there is the possibility of defining a list of available values
with the `choices` variable's parameter:
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_010/firefox/00-proxy.yml
:linenos:
:language: yaml
:caption: The `proxy_mode` variable with the `choice` parameter
:name: RougailDictionaryChoiceType
..
---
proxy_mode:
description: Configure Proxy Access to the Internet
choices:
- No proxy
- Auto-detect proxy settings for this network
- Use system proxy settings
- Manual proxy configuration
- Automatic proxy configuration URL
default: No proxy
:tutorial:`Download this file from the rougail-tutorials git repository <src/tag/v1.1_010/firefox/00-proxy.yml>`
Let's run the Rougail CLI with these available values:
.. code-block:: text
:class: terminal
rougail -m firefox/
We have an output like this one:
.. raw:: html
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/commit/v1.1_010/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>
`No proxy` is an available variable's value. We say that the `proxy_mode` variable is *constrained*
by the possibilities of the `choice` parameter.
.. type-along:: Let's add some user datas to this structure
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_010/config/03/config.yml
:linenos:
:language: yaml
:caption: A user data specification
:tutorial:`Download this file from the rougail-tutorials git repository <src/tag/v1.1_010/config/03/config.yml>`
If we run the Rougail CLI with this user datas:
.. raw:: html
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_010/config/03/cmd_ro.txt
:class: terminal
..
rougail -m firefox/ -u yaml -yf config/03/config.yml
We have this output:
.. raw:: html
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_010/config/03/output_ro.html
:class: output
..
<pre>╭────────────── Caption ───────────────╮
│ Variable Modified value │
│ (<span style="color: #00aa00">⏳ Original default value</span>) │
╰──────────────────────────────────────╯
Variables:
<span style="color: #5c5cff">┗━━ </span>📓 Configure Proxy Access to the Internet: Manual proxy configuration ◀ loaded from the YAML file "config/03/config.yml" (⏳ <span style="color: #00aa00">No proxy</span>)
</pre>
As we set the `proxy_mode` variable from a user data file,
we now have specified a value which is **not** a default value, and
the output of the Rougail CLI explicitly shows that a user data value has been entered,
it shows which user data file this value comes from, and it also indicates
what the default value is for information purposes.
.. type-along:: The constraints that come with the choices
The `proxy_mode` variable's possible values is *constrained*.
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`
.. questions:: Question
What happens if I set a value that is not available in the choices?
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_010/config/04/config.yml
:linenos:
:language: yaml
:caption: A (false) user data specification
:tutorial:`Download this file from the rougail-tutorials git repository <src/tag/v1.1_010/config/04/config.yml>`
If we run the Rougail CLI with this user datas:
.. raw:: html
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_010/config/04/cmd_ro.txt
:class: terminal
..
rougail -m firefox/ -u yaml -yf config/03/config.yml
We have this output:
.. raw:: html
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_010/config/04/output_ro.html
:class: output
We can see here that Rougail warns us about an invalid value that is not in the available choices,
that's why this value will not be used and it falls back to the original default value.
But maybe this is not the behavior you need. Maybe you need to stop everything if Rougail detects that
something is going wrong, maybe you need some kind of a strict mode.
Indeed, this warning can be transformed into an error.
If we run the Rougail CLI with this `--cli.invalid_user_datas_error` parameter:
.. raw:: html
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_010/config/04/cmd_invalid.txt
:class: terminal
..
rougail -m firefox/ -u yaml -yf config/03/config.yml --cli.invalid_user_datas_error
Then we have an `error` output instead of a `warning` output:
.. raw:: html
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_010/config/04/output_invalid.html
:class: output
..
<pre><span style="font-weight: bold; color: #ff0000">🛑 ERRORS</span>
<span style="color: #ff0000">┗━━ </span>the value "foo" is an invalid choice for "proxy_mode" (Configure Proxy Access to the Internet), only "Auto-detect proxy settings for this network", "Automatic proxy configuration URL", "Manual proxy
<span style="color: #ff0000"> </span>configuration", "No proxy" and "Use system proxy settings" are allowed, it will be ignored when loading from the YAML file "config/04/config.yml"
</pre>
.. keypoints:: Key points progress
Indeed, in the Firefox configuration, it is possible to define several configuration modes,
from no proxy at all to different kind of automatic or manual configuration modes.
The choices, the list of available values for a variable, can help us to handle this situation.
**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_010/firefox/00-proxy.yml
:linenos:
:language: yaml
:caption: A Rougail structure file with a variable named `proxy_mode`, with a default value.
..
.. raw:: text
---
proxy_mode:
description: Configure Proxy Access to the Internet
choices:
- No proxy
- Auto-detect proxy settings for this network
- Use system proxy settings
- Manual proxy configuration
- Automatic proxy configuration URL
default: No proxy

808
docs/tutorial/disabled.rst Normal file
View file

@ -0,0 +1,808 @@
Define access to variable or family
==========================================
.. objectives:: Objectives
In this section we will see what a disabled variable or family is, and why it can be interesting
to assign the `disabled` property to a variable or a family.
Then we'll see the same thing for the `hidden` property.
We'll also learn the difference between disabling and hiding families or variables.
We will:
- create a `disabled` family
- use a new family or variable's property: the `hidden` property
Disabling and hiding are two families or variables properties.
.. prerequisites:: Prerequisites
- We assume that Rougail's library is :ref:`installed <installation>` on your computer.
- It is possible to retrieve the current state of the various Rougail files manipulated in this tutorial step
by checking out the corresponding tag of the `rougail-tutorials` git repository.
Each tag corresponds to a stage of progress in the tutorial.
Of course, you can also decide to copy/paste or download the tutorial files contents while following the tutorial steps.
If you want to follow this tutorial with the help of the corresponding :tutorial:`rougail-tutorials git repository <src/branch/1.1>`,
this workshop page corresponds to the tags :tutorial:`v1.1_050 <src/tag/v1.1_050>` to :tutorial:`v1.1_053 <src/tag/v1.1_053>`
in the repository.
::
git clone https://forge.cloud.silique.fr/stove/rougail-tutorials.git
git switch --detach v1.1_050
.. type-along:: What a property is
Let's begin with defining what a property is:
.. glossary::
property
A property is a state (`disabled`, `mandatory`, `frozen`, `hidden`...)
of a family, a subfamily or a variable.
These properties change the usual behavior of a variable or family.
A disabled family
------------------
Here we are going to assign the `disabled` property to the `manual` family:
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_050/firefox/10-manual.yml
:language: yaml
:caption: The `manual` family has the `disabled` property set in this :file:`firefox/10-manual.yml` structure file
..
%YAML 1.2
---
version: 1.1
manual:
description: Manual proxy configuration
disabled: true
http_proxy: # HTTP Proxy
address:
description: HTTP address
type: domainname
params:
allow_ip: true
port:
description: HTTP Port
type: port
default: 8080
Notice that we have this `disabled: true` property assigned to the `manual` family.
Let's launch the Rougail CLI on this structure file (whith an empty user data file):
.. raw:: html
:class: terminal
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_050/config/01/cmd_ro.txt
..
rougail -m firefox/ -u yaml -yf config/02/config.yml
The Rougail CLI outputs this:
.. raw:: html
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_050/config/01/output_ro.html
:class: output
We can deduce from the Rougail CLI output that the `manual` family is not taken into account by Rougail.
So what does this disabled property exactly?
.. glossary::
disabled
The disabled property is a property that can be assigned to a variable or a family.
It makes the :term:`configuration` act as if the variable or family that has this property has not even been defined.
It simply doesn't exist (it is deactivated) for the whole configuration.
.. note:: Note that if a family has been disabled, all variables and sub-families that it contains are disabled.
And if we try to assign values to variables that have been disabled, here is what happens:
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_050/config/02/config.yml
:language: yaml
:caption: In this :file:`config/02/config.yml` user data file, we assign values to variables that have been disabled
If we launch the Rougail CLI:
.. raw:: html
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/commit/v1.1_050/config/02/cmd_unknown.txt
:class: terminal
..
rougail -m firefox/ -u yaml -yf config/02/config.yml --cli.unknown_user_data_error
.. note:: The `--cli.unknown_user_data_error` option changes the behaviour of the Rougail CLI's standard output:
when an unknown (or disabled or hidden) variable is declared in the :term:`user data file <user data>`
then it appears in the output as an error instead of a warning.
It outputs:
.. raw:: html
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/commit/v1.1_050/config/02/output_unknown.html
:class: output
..
<pre><span style="font-weight: bold; color: #ff0000">🛑 Caution</span>
<span style="color: #ff0000">┗━━ </span>manual (Manual proxy configuration)
<span style="color: #ff0000"> </span><span style="color: #ff0000">┣━━ </span>http_proxy (HTTP Proxy)
<span style="color: #ff0000"> </span><span style="color: #ff0000">┃ </span><span style="color: #ff0000">┣━━ </span>address (HTTP address): <span style="color: #ff0000">🛑 family "manual" (Manual proxy </span>
<span style="color: #ff0000"> </span><span style="color: #ff0000">┃ </span><span style="color: #ff0000">┃ </span><span style="color: #ff0000">configuration) has property disabled, so cannot access to "address" </span>
<span style="color: #ff0000"> </span><span style="color: #ff0000">┃ </span><span style="color: #ff0000">┃ </span><span style="color: #ff0000">(HTTP address), it has been loading from the YAML file </span>
<span style="color: #ff0000"> </span><span style="color: #ff0000">┃ </span><span style="color: #ff0000">┃ </span><span style="color: #ff0000">"config/02/config.yml"</span>
<span style="color: #ff0000"> </span><span style="color: #ff0000">┃ </span><span style="color: #ff0000">┗━━ </span>port (HTTP Port): <span style="color: #ff0000">🛑 family "manual" (Manual proxy configuration) </span>
<span style="color: #ff0000"> </span><span style="color: #ff0000">┃ </span><span style="color: #ff0000"> </span><span style="color: #ff0000">has property disabled, so cannot access to "port" (HTTP Port), it </span>
<span style="color: #ff0000"> </span><span style="color: #ff0000">┃ </span><span style="color: #ff0000"> </span><span style="color: #ff0000">has been loading from the YAML file "config/02/config.yml"</span>
<span style="color: #ff0000"> </span><span style="color: #ff0000">┗━━ </span>use_for_https (Also use this proxy for HTTPS): <span style="color: #ff0000">🛑 family "manual" </span>
<span style="color: #ff0000"> </span><span style="color: #ff0000"> </span><span style="color: #ff0000">(Manual proxy configuration) has property disabled, so cannot access to </span>
<span style="color: #ff0000"> </span><span style="color: #ff0000"> </span><span style="color: #ff0000">"use_for_https" (Also use this proxy for HTTPS), it has been loading </span>
<span style="color: #ff0000"> </span><span style="color: #ff0000"> </span><span style="color: #ff0000">from the YAML file "config/02/config.yml"</span>
</pre>
We can see that the Rougail CLI is warning us about the variables that we are trying to assign values on which are disabled.
Because it is not logical. We are trying to assign values to variables that are not taken into account in the :term:`configuration`.
The point is that we disable them in order to expose the fact that we don't use them,
but it's not just that: if we fill them in, there might be a problem in the overall integrity of the whole :term:`configuration`.
We shall fill and use in these variables in the `Manual proxy configuration` use case context only.
Otherwise, **we need to disable them when they are not used**.
In a practical point of view, if we fill them in, Rougail CLI will output a warning and the :term:`operator` will see it.
He will wonder : "oh, what am I doing?", I shall fill and use in these variables only in the `Manual proxy configuration` context.
A conditional disabled family
------------------------------
Let's look again at our use case. We have a choice between five options
in order to set the proxy mode:
.. image:: images/firefox_01.png
These five choices are:
- No proxy
- Auto-detect proxy settings for this network
- Use system proxy settings
- Manual proxy configuration
- Automatic proxy configuration URL
Actually if the `Manual proxy configuration` is not selected, we don't need to set
these `address` and `port` variables, there is no point in setting them in
four out of our five use cases.
.. important:: We need to **disable** variables or families that are not used
in a given usage context.
Disabling variables one by one can be replaced by disabling a whole family.
If we don't choose the manual mode, we need to **disable** the whole `manual` family, it will disable
all the subfamilies and the variables in it.
Note that we've placed theses variables in the `http_proxy`
subfamily. We can then disable it or even the parent `manual` subfamily in order to
disable the `http_proxy` family and all the variables that are placed in it, because
the `manual` family variables shall be used only in the manual proxy use case context.
Notice the `disabled: true` parameter set in the `manual` family:
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_050/firefox/10-manual.yml
:linenos:
:language: yaml
:caption: The `http_proxy` subfamily in the :file:`firefox/10-manual.yml` structure file is disabled here
..
---
manual:
description: Manual proxy configuration
disabled: true
http_proxy:
description: HTTP Proxy
address:
description: HTTP address
type: domainname
params:
allow_ip: true
port:
description: HTTP Port
type: port
default: 8080
.. type-along:: For those who follow the tutorial with the help of the git repository
Now you need to checkout the `v1.1_051` version::
git switch --detach v1.1_051
What could be usefull here is a *dynamically* disable or enable the `manual` family.
The idea in this section is to dynamically set the enable/disable property according to the chosen use case context.
In rougail, we can set a property's value **depending on** the value of another variable.
**The property's value is conditioned by** another variable.
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_051/firefox/10-manual.yml
:linenos:
:language: yaml
:caption: The :file:`firefox/10-manual.yml` structure file. The `manual` family dynamically enabled or disabled
..
%YAML 1.2
---
version: 1.1
manual:
description: Manual proxy configuration
disabled:
variable: _.proxy_mode
when_not: Manual proxy configuration
http_proxy: # HTTP Proxy
address:
description: HTTP address
type: domainname
params:
allow_ip: true
port:
description: HTTP Port
type: port
default: 8080
Now the `disabled` property has some parameter defined. First, let's explaine the `variable` parameter.
This parameter specifies the target variable. The value of this target variable
will be used to dynamically enable or disable our `manual` family.
We can see here that the `manual` family disabled or enabled property is contitionned by the `_.proxy_mode` variable's value.
The target variable is `_.proxy_mode`.
.. note:: The `_.` notation means the current path of the family you're currently in.
In the python quasi algorithmic notation we could say that:
.. code-block:: python
_.proxy_mode == proxy_mode
This is true only because in our use case `proxy_mode` is located on the root path.
Now regarding the `when_not` parameter, this means that if the target variable's value
is `Manual proxy configuration` then the `manual` familiy **will not** be disabled
(that is, it will be **enabled**).
Regarding as the default value use case, the `proxy_mode`'s variable is `No proxy` by default.
The `manual` familiy is then **disabled by default**.
Let's launch the Rougail CLI on an empty user value file:
.. raw:: html
:class: terminal
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_051/config/01/cmd_ro.txt
..
rougail -m firefox/ -u yaml -yf config/01/config.ym
We have this output:
.. raw:: html
:class: output
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_051/config/01/output_ro.html
..
<pre>╭──────────────────── Caption ─────────────────────╮
│ <span style="color: #ff0000">Undocumented but modified variable</span> <span style="color: #ffd700">Default value</span> │
╰──────────────────────────────────────────────────╯
Variables:
<span style="color: #5c5cff">┗━━ </span>📓 <span style="color: #ff0000">Configure Proxy Access to the Internet</span>: <span style="color: #ffd700">No proxy</span>
</pre>
We can see that the `manual` family and all the variables into it are not present.
.. type-along:: Dynamically enabling the `manual` family
Now we are going to choose the **manual mode**, that is the `Manual proxy configuration`
value for the `proxy_mode` variable, and things will become slightly different.
If the manual mode for the proxy is not selected, then the `manual` family is disabled.
On the other hand, if the manual proxy's configuration mode is selected,
then the `manual` family **is enabled**:
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_051/config/02/config.yml
:linenos:
:language: yaml
:caption: The `proxy_mode`'s manual setting in the :file:`config/02/config.yaml` user datas file
..
---
proxy_mode: Manual proxy configuration
manual:
http_proxy:
address: http.proxy.net
port: 3128
use_for_https: false
Let's launch the Rougail CLI to verify this:
.. raw:: html
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_051/config/02/cmd_ro.txt
:class: terminal
..
rougail -m firefox/ -u yaml -yf config/02/config.yml
It outputs:
.. raw:: html
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_051/config/02/output_ro.html
:class: output
..
<pre>╭────────────── Caption ───────────────╮
│ Variable <span style="color: #00aa00">Modified value</span> │
│ (⏳ Original default value) │
╰──────────────────────────────────────╯
Variables:
<span style="color: #5c5cff">┣━━ </span>📓 proxy_mode (Configure Proxy Access to the Internet): <span style="color: #00aa00">Manual proxy </span>
<span style="color: #5c5cff">┃ </span><span style="color: #00aa00">configuration</span> ◀ loaded from the YAML file "config/02/config.yml" (⏳ No
<span style="color: #5c5cff">┃ </span>proxy)
<span style="color: #5c5cff">┗━━ </span>📂 manual (Manual proxy configuration)
<span style="color: #5c5cff"> </span><span style="color: #5c5cff">┣━━ </span>📂 http_proxy (HTTP Proxy)
<span style="color: #5c5cff"> </span><span style="color: #5c5cff">┃ </span><span style="color: #5c5cff">┣━━ </span>📓 address (HTTP address): <span style="color: #00aa00">http.proxy.net</span> ◀ loaded from the YAML
<span style="color: #5c5cff"> </span><span style="color: #5c5cff">┃ </span><span style="color: #5c5cff">┃ </span>file "config/02/config.yml"
<span style="color: #5c5cff"> </span><span style="color: #5c5cff">┃ </span><span style="color: #5c5cff">┗━━ </span>📓 port (HTTP Port): <span style="color: #00aa00">3128</span> ◀ loaded from the YAML file
<span style="color: #5c5cff"> </span><span style="color: #5c5cff">┃ </span><span style="color: #5c5cff"> </span>"config/02/config.yml" (⏳ 8080)
<span style="color: #5c5cff"> </span><span style="color: #5c5cff">┗━━ </span>📓 use_for_https (Also use this proxy for HTTPS): <span style="color: #00aa00">false</span> ◀ loaded from
<span style="color: #5c5cff"> </span><span style="color: #5c5cff"> </span>the YAML file "config/02/config.yml" (⏳ true)
</pre>
.. rubric:: Explanation
Here the `disabled` property **depends on** the value of the `proxy_mode` variable.
It is the `variable` parameter that allows you to define the name of the target variable on which the `disabled` property depends.
Please remember that this activation/deactivation of the `manual` family
depends on the value of the `proxy_mode` variable. Here we have
chosen the `Manual proxy configuration` value, so the `address` and `port` variables appear.
A hidden family
-------------------
.. type-along:: For those who follow the tutorial with the help of the git repository
Now you need to checkout the `v1.1_052` version::
git switch --detach v1.1_052
Let's introduce a new property here:
.. glossary::
hidden
A variable or family's property is hidden if its value **shall not be seen** in a given :term:`context`.
Anyway, these variables can be used if the context evolves.
This is the main difference between the `hidden` and the `disabled` properties:
- with the `disabled` property, the variables are *deactivated*
- with the `hidden` property, the variables are just not seen when loading the user data.
Now we can set a `hidden` property to the `https_proxy` family:
Here is our new :file:`20-manual.yml` structure file:
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_052/firefox/20-manual.yml
:linenos:
:language: yaml
:caption: The :file:`firefox/20-manual.yml` structure file with the `hidden` property on the `https_proxy` family.
..
%YAML 1.2
---
version: 1.1
manual:
use_for_https: true # Also use this proxy for HTTPS
https_proxy:
description: HTTPS Proxy
hidden: true
address:
description: HTTPS address
default:
variable: __.http_proxy.address
port:
description: HTTPS Port
default:
variable: __.http_proxy.port
...
.. confval:: https_proxy.address
:type: `domainname`
This is an address setting for the manual HTTPS configuration
.. confval:: https_proxy.port
:type: `port`
This is a port setting for the manual HTTPS configuration
We have now a `hidden` property assigned to the `https_proxy` family, which is
hiding these two variables.
.. questions:: Why a `hidden` property?
Here is a detailed explanation of this choice of the `hidden` property:
We are in a use case where we want the HTTP mode configuration to be identical to the HTTPS mode configuration, so:
- we need to have access to the HTTP mode configuration details
- this HTTP configuration will be duplicated using references to the default values,
so there is no need to request intervention from the :term:`operator`
to fill in the details of the default HTTPS configuration
- however, the :term:`operator` may want to have access to the HTTPS mode configuration if he wishes to,
in order to assign customised values to it.
Therefore, the :term:`operator` must obviously be able to access this HTTPS configuration.
If we launch the Rougail CLI on the this user data
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_052/config/01/config.yml
:linenos:
:language: yaml
:caption: The :file:`config/01/config.yml` user data file with the `hidden` property on the `https_proxy` family.
..
---
proxy_mode: Manual proxy configuration
manual:
http_proxy:
address: http.proxy.net
port: 3128
use_for_https: false
https_proxy:
address: https.proxy.net
Let's launch the Rougail in read only (`RO`) mode first:
.. raw:: html
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_052/config/01/cmd_ro.txt
:class: terminal
..
rougail -m firefox/ -u yaml -yf config/01/config.yml
We have this output:
.. raw:: html
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_052/config/01/output_ro.html
:class: output
..
<pre><span style="font-weight: bold; color: #ffff00">🔔 Warning</span>
<span style="color: #ffff00">┗━━ </span>manual (Manual proxy configuration)
<span style="color: #ffff00"> </span><span style="color: #ffff00">┗━━ </span>https_proxy (HTTPS Proxy)
<span style="color: #ffff00"> </span><span style="color: #ffff00"> </span><span style="color: #ffff00">┗━━ </span>address (HTTPS address): <span style="color: #ffff00">🔔 family "https_proxy" (HTTPS Proxy) has </span>
<span style="color: #ffff00"> </span><span style="color: #ffff00"> </span><span style="color: #ffff00"> </span><span style="color: #ffff00">property hidden, so cannot access to "address" (HTTPS address), it </span>
<span style="color: #ffff00"> </span><span style="color: #ffff00"> </span><span style="color: #ffff00"> </span><span style="color: #ffff00">will be ignored when loading from the YAML file </span>
<span style="color: #ffff00"> </span><span style="color: #ffff00"> </span><span style="color: #ffff00"> </span><span style="color: #ffff00">"config/01/config.yml"</span>
╭────────────── Caption ───────────────╮
│ Variable <span style="color: #00aa00">Modified value</span> │
│ (⏳ Original default value) │
╰──────────────────────────────────────╯
Variables:
<span style="color: #5c5cff">┣━━ </span>📓 proxy_mode (Configure Proxy Access to the Internet): <span style="color: #00aa00">Manual proxy </span>
<span style="color: #5c5cff">┃ </span><span style="color: #00aa00">configuration</span> ◀ loaded from the YAML file "config/01/config.yml" (⏳ No
<span style="color: #5c5cff">┃ </span>proxy)
<span style="color: #5c5cff">┗━━ </span>📂 manual (Manual proxy configuration)
<span style="color: #5c5cff"> </span><span style="color: #5c5cff">┣━━ </span>📂 http_proxy (HTTP Proxy)
<span style="color: #5c5cff"> </span><span style="color: #5c5cff">┃ </span><span style="color: #5c5cff">┣━━ </span>📓 address (HTTP address): <span style="color: #00aa00">http.proxy.net</span> ◀ loaded from the YAML
<span style="color: #5c5cff"> </span><span style="color: #5c5cff">┃ </span><span style="color: #5c5cff">┃ </span>file "config/01/config.yml"
<span style="color: #5c5cff"> </span><span style="color: #5c5cff">┃ </span><span style="color: #5c5cff">┗━━ </span>📓 port (HTTP Port): <span style="color: #00aa00">3128</span> ◀ loaded from the YAML file
<span style="color: #5c5cff"> </span><span style="color: #5c5cff">┃ </span><span style="color: #5c5cff"> </span>"config/01/config.yml" (⏳ 8080)
<span style="color: #5c5cff"> </span><span style="color: #5c5cff">┗━━ </span>📓 use_for_https (Also use this proxy for HTTPS): <span style="color: #00aa00">false</span> ◀ loaded from
<span style="color: #5c5cff"> </span><span style="color: #5c5cff"> </span>the YAML file "config/01/config.yml" (⏳ true)
</pre>
Now let's launch the Rougail CLI in read write mode (`RW`) on the same user data:
.. raw:: html
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_052/config/01/cmd_rw.txt
:class: terminal
We have this output:
.. raw:: html
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_052/config/01/output_rw.html
:class: output
.. type-along:: The read only mode view and the read write mode view
.. FIXME: à unifier avec l'autre définition de ro et rw dans la page dédiée rougail/rw_ro_modes.html
.. glossary::
read only mode
We call this mode the `RO` mode. In this mode it is impossible to modify the values of the variables.
This is the standard configuration usage mode.
read write mode
We call this mode the `RW` mode. In this mode you can edit the variables, even these that have
been hidden or disabled.
Why these modes ? In this way, the :term:`operator` or the :term:`integrator` don't have to
add or pop familiy properties each time we pass from one use (editing mode) to another
(configuration using mode) to an other. Swithching modes with setting properties is not a good idea.
It's better to change the read write and the read_only mode inside a Rougail CLI session.
.. note:: During a standard Rougail CLI session, the default usage is the read only mode.
We can switch at any time tho the read write mode by adding the `--cli.read_write`
Rougail CLI parameter.
In the both `RO` and `RW` modes of the Rougail CLI session, we have this warning:
::
🔔 Warning
┗━━ manual (Manual proxy configuration)
┗━━ https_proxy (HTTPS Proxy)
┗━━ address (HTTPS address): 🔔 family "https_proxy" (HTTPS Proxy) has
property hidden, so cannot access to "address" (HTTPS address), it
will be ignored when loading from the YAML file
"config/01/config.yml"
We are warned that the `https_proxy` family has the `hidden` property.
Note that this is only in the read only mode that the variables that lives in the `https_proxy` familiy are
set as **unmodifiable variable**:
::
┗━━ 📂 https_proxy (HTTPS Proxy)
┣━━ 📓 address (HTTPS address): http.proxy.net
┗━━ 📓 port (HTTPS Port): 3128
It is logical that we don't have this unmodifiable setting in the read write mode,
because the read/write mode is designed to be an editing mode.
.. questions:: Question: shall we use the `disabled` property here?
Is it relevant to use the :term:`disabled property <disabled>` here?
**answer**: No! Because we *need* to use these variables at any :term:`context` of the proxy's manual configuration use case,
we simply have to point their values in one direction or another depending on this or that context,
that's why it is absolutely not a question of disabling them. The `manual.https_proxy.address`
and the `manual.http_proxy.port` variables shall not be disabled in the manual mode.
A conditional hidden family
----------------------------
.. type-along:: For those who follow the tutorial with the help of the git repository
Now you need to checkout the `v1.1_053` version::
git switch --detach v1.1_053
Now we will focus on configuring the HTTPS mode in case of `"Manual proxy configuration"` value has been chosen,
let's have a look at our use case again:
.. image:: images/firefox_manual_https.png
Let's have a look at the HTPPS configuration corresponding structure file:
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/commit/v1.1_053/firefox/20-manual.yml
:linenos:
:language: yaml
:caption: the :file:`firefox/20-manual.yml` structure file
..
%YAML 1.2
---
version: 1.1
manual:
use_for_https: true # Also use this proxy for HTTPS
https_proxy:
description: HTTPS Proxy
hidden:
variable: _.use_for_https
address:
description: HTTPS address
default:
variable: __.http_proxy.address
port:
description: HTTPS Port
default:
variable: __.http_proxy.port
...
We have added a new variable, named `use_for_https` here:
.. confval:: use_for_https
:type: `boolean`
:default: `true`
This is a setting that enables to reuse or not the HTTP proxy configuration for HTTPS
The variable that drives the hidden/show behavior is the `use_for_https` variable because the `hidden` property has
a `variable` target parameter that points to it: `variable: _.use_for_https`.
.. prerequisites:: Reminder
The underscore and the point before the variable (`_.use_for_https`) points to the variable that lives in the same
family.
Let's introduce a new Rougail concept here:
.. glossary::
context
A :term:`configuration` is highly statefull and can change at any moment.
Sometimes somes minor changes in the :term:`user data <user data>` may involve chain reactions
in the whole :term:`configuration`.
The **context** is the state of the user data at one moment, the set of the values of the variables
at a given moment.
This term refers in Rougail to the ability of a system to handle
the *statefull* state of a configuration.
It expresses the transition between one situation to another situation,
that is, the deeply **statefull** aspects of a data set.
Do we want to reuse, for the HTTPS mode, the same configuration as for the HTTP mode?
Well, it depends on the :term:`context`.
.. questions:: Question: how does it work?
How will this variable drive the reuse of HTTP data to HTTPS data?
With this :confval:`use_for_https` boolean variable, there are two possibilities, and only two:
- The http proxy's configuration will be reused for the https proxy's configuration
- The http proxy's will not be reused for the https proxy's configuration
Here is an example with different user values for handling HTTP and HTTPS:
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/commit/v1.1_053/config/01/config.yml
:linenos:
:language: yaml
:caption: User datas in the user data file :file:`config/01/config.yml` with `use_for_https` as false
..
---
proxy_mode: Manual proxy configuration
manual:
http_proxy:
address: http.proxy.net
port: 3128
use_for_https: false
https_proxy:
address: https.proxy.net
If we launch the Rougail CLI:
.. raw:: html
:class: terminal
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/commit/v1.1_053/config/01/cmd_ro.txt
..
rougail -m firefox/ -u yaml -yf config/01/config.yml
We have this output:
.. raw:: html
:class: output
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/commit/v1.1_053/config/01/output_ro.html
..
<pre>╭────────────── Caption ───────────────╮
│ Variable <span style="color: #ffd700">Default value</span> │
│ <span style="color: #00aa00">Modified value</span> │
│ (⏳ Original default value) │
╰──────────────────────────────────────╯
Variables:
<span style="color: #5c5cff">┣━━ </span>📓 proxy_mode (Configure Proxy Access to the Internet): <span style="color: #00aa00">Manual proxy </span>
<span style="color: #5c5cff">┃ </span><span style="color: #00aa00">configuration</span> ◀ loaded from the YAML file "config/01/config.yml" (⏳ No
<span style="color: #5c5cff">┃ </span>proxy)
<span style="color: #5c5cff">┗━━ </span>📂 manual (Manual proxy configuration)
<span style="color: #5c5cff"> </span><span style="color: #5c5cff">┣━━ </span>📂 http_proxy (HTTP Proxy)
<span style="color: #5c5cff"> </span><span style="color: #5c5cff">┃ </span><span style="color: #5c5cff">┣━━ </span>📓 address (HTTP address): <span style="color: #00aa00">http.proxy.net</span> ◀ loaded from the YAML
<span style="color: #5c5cff"> </span><span style="color: #5c5cff">┃ </span><span style="color: #5c5cff">┃ </span>file "config/01/config.yml"
<span style="color: #5c5cff"> </span><span style="color: #5c5cff">┃ </span><span style="color: #5c5cff">┗━━ </span>📓 port (HTTP Port): <span style="color: #00aa00">3128</span> ◀ loaded from the YAML file
<span style="color: #5c5cff"> </span><span style="color: #5c5cff">┃ </span><span style="color: #5c5cff"> </span>"config/01/config.yml" (⏳ 8080)
<span style="color: #5c5cff"> </span><span style="color: #5c5cff">┣━━ </span>📓 use_for_https (Also use this proxy for HTTPS): <span style="color: #00aa00">false</span> ◀ loaded from
<span style="color: #5c5cff"> </span><span style="color: #5c5cff">┃ </span>the YAML file "config/01/config.yml" (⏳ true)
<span style="color: #5c5cff"> </span><span style="color: #5c5cff">┗━━ </span>📂 https_proxy (HTTPS Proxy)
<span style="color: #5c5cff"> </span><span style="color: #5c5cff"> </span><span style="color: #5c5cff">┣━━ </span>📓 address (HTTPS address): <span style="color: #00aa00">https.proxy.net</span> ◀ loaded from the YAML
<span style="color: #5c5cff"> </span><span style="color: #5c5cff"> </span><span style="color: #5c5cff">┃ </span>file "config/01/config.yml"
<span style="color: #5c5cff"> </span><span style="color: #5c5cff"> </span><span style="color: #5c5cff">┗━━ </span>📓 port (HTTPS Port): <span style="color: #ffd700">8080</span>
</pre>
Notice that we have this `use_for_https` `boolean` type variable, its default value is `True`.
We want to offer the possibility of providing an identical or possibly different proxy configuration for the HTTP and for the HTTPS protocols.
Here is an example with identical HTTP and HTTPS proxy configuration:
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/commit/v1.1_053/config/02/config.yml
:linenos:
:language: yaml
:caption: User datas in the user data file :file:`config/02/config.yml` with `use_for_https` as true
..
---
proxy_mode: Manual proxy configuration
manual:
http_proxy:
address: http.proxy.net
port: 3128
use_for_https: true
Let's launch the Rougail CLI:
.. raw:: html
:class: terminal
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/commit/v1.1_053/config/02/cmd_ro.txt
..
rougail -m firefox/ -u yaml -yf config/02/config.yml
We have this output:
.. raw:: html
:class: output
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/commit/v1.1_053/config/02/output_ro.html
..
<pre><span style="font-weight: bold; color: #ff0000">🛑 Caution</span>
<span style="color: #ff0000">┗━━ </span>manual (Manual proxy configuration)
<span style="color: #ff0000"> </span><span style="color: #ff0000">┗━━ </span>https_proxy (HTTPS Proxy)
<span style="color: #ff0000"> </span><span style="color: #ff0000"> </span><span style="color: #ff0000">┗━━ </span>address (HTTPS address): <span style="color: #ff0000">🛑 mandatory variable but is inaccessible </span>
<span style="color: #ff0000"> </span><span style="color: #ff0000"> </span><span style="color: #ff0000"> </span><span style="color: #ff0000">and has no value</span>
</pre>
Which is logical, HTTPS proxy variables have no values set yet.
We are going to see how to point HTTPS variables to HTTP variables.
.. keypoints:: Key points progress
**summary**
We have now the ability to build *contextual settings*:
- if the `proxy_mode` variable's value is not `'Manual proxy configuration'` the `manual` family is disabled
- if the `proxy_mode` variable's value is `'Manual proxy configuration'` then the `manual` family is enabled
- if the `use_for_https` variable's value is `true`, the HTTP configuration will be reused in the HTTPS situation
and the `https_proxy` family will be hidden
- if the `manual.https_proxy.address` has no value set, the defaut value is the same as the `manual.http_proxy.address`'s value
(same behavior with the HTTP port and the HTTPS port variable)
And yes, we did it. We have arrived at the end of the proxy's manual configuration's section.
**Keywords**
- We now know what a *property* is, we have seen in details the :term:`disabled` property,
- We can target a variable's value in the `disabled` property's value,
we call it a variable based contextual disabled family,
- The :term:`hidden` property can be set to a family,
- The fact that a property can be set dynamically,
- The conditional dependency of a `hidden` property can depends on a `boolean` variable,
- We know what a calculated default value is.

View file

@ -0,0 +1,458 @@
Some suitable types
=====================
.. objectives:: Objectives
There isn't just the basic `string` available type, here we will discover new
variable types, for example the `boolean` type, and even types that are much more
suited to our use case, such as `domainname` or `port`.
.. prerequisites:: Prerequisites
- We assume that Rougail's library is :ref:`installed <installation>` on your computer.
- It is possible to retrieve the current state of the various Rougail files manipulated in this tutorial step
by checking out the corresponding tag of the `rougail-tutorials` git repository.
Each tag corresponds to a stage of progress in the tutorial.
Of course, you can also decide to copy/paste or download the tutorial files contents while following the tutorial steps.
If you want to follow this tutorial with the help of the corresponding :tutorial:`rougail-tutorials git repository <src/branch/1.1>`,
this workshop page corresponds to the tags :tutorial:`v1.1_030 <src/tag/v1.1_030>` to :tutorial:`v1.1_033 <src/tag/v1.1_033>`
in the repository.
::
git clone https://forge.cloud.silique.fr/stove/rougail-tutorials.git
git switch --detach v1.1_030
.. type-along:: Let's recap how far we've come
We have an `http_proxy` family with an `address` variable in it.
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_022/firefox/10-manual.yml
:language: yaml
:caption: An `address` variable in the `http_proxy` family
..
manual: # Manual proxy configuration
http_proxy: # HTTP Proxy
address:
description: HTTP address
:tutorial:`Download this file from the rougail-tutorials git repository <src/tag/v1.1_022/firefox/10-manual.yml>`
A variable with type `domainname`
-----------------------------------
We will add a business types to our `address` variable:
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_030/firefox/10-manual.yml
:language: yaml
:caption: An `address` variable in the `http_proxy` family
..
manual: # Manual proxy configuration
http_proxy: # HTTP Proxy
address:
description: HTTP address
type: domainname
:tutorial:`Download this file from the rougail-tutorials git repository <src/tag/v1.1_030/firefox/10-manual.yml>`
Notice that with this `type: domainname` we have assigned the `domainname` business type to this variable.
Assigning a type is convenient for reading, but what else does it bring?
Well, with a correct user data like this one:
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_030/config/01/config.yml
:language: yaml
:caption: A domain name user data setting
..
---
manual:
http_proxy:
address: net.example
:tutorial:`Download this file from the rougail-tutorials git repository <src/tag/v1.1_030/config/01/config.yml>`
if we launch the Rougail CLI on it:
.. raw:: html
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_030/config/01/cmd_ro.txt
:class: terminal
..
rougail -m firefox/ -u yaml -yf config/01/config.yml
We have this output:
.. raw:: html
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_030/config/01/output_ro.html
:class: output
..
<pre>╭──────── Caption ────────╮
│ Variable <span style="color: #ffd700">Default value</span> │
│ <span style="color: #00aa00">Modified value</span> │
╰─────────────────────────╯
Variables:
<span style="color: #5c5cff">┣━━ </span>📓 Configure Proxy Access to the Internet: <span style="color: #ffd700">No proxy</span>
<span style="color: #5c5cff">┗━━ </span>📂 Manual proxy configuration
<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>📓 HTTP address: <span style="color: #00aa00">example.net</span> ◀ loaded from the YAML file
<span style="color: #5c5cff"> </span><span style="color: #5c5cff"> </span><span style="color: #5c5cff"> </span>"config/01/config.yml"
</pre>
And we don't really see any change associated with the fact that we have assigned
a type to this variable. But if we assign this (wrong) user data:
.. type-along:: A domain name has no space in it
Let's have a look at an example of user setting that does not fit the
`domainname` type:
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_030/config/03/config.yml
:language: yaml
:caption: An invalid domain name for the :file:`config/03/config.yml` user data setting
..
---
manual:
http_proxy:
address: bla bla
:tutorial:`Download this file from the rougail-tutorials git repository <src/tag/v1.1_030/config/03/config.yml>`
The value is obviously not a domain name, then when we will launch the Rougail CLI:
.. raw:: html
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_030/config/03/cmd_invalid.txt
:class: terminal
..
rougail -m firefox/ -u yaml -yf config/03/config.yml --cli.invalid_user_datas_error
we then have this output:
.. raw:: html
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_030/config/03/output_invalid.html
:class: error
..
<pre><span style="font-weight: bold; color: #ff0000">🛑 ERRORS</span>
<span style="color: #ff0000">┗━━ </span>the value "bla bla" is an invalid domain name for
<span style="color: #ff0000"> </span>"manual.http_proxy.address" (HTTP address), must not be an IP, it will be
<span style="color: #ff0000"> </span>ignored when loading from the YAML file "config/02/config.yml"
</pre>
A variable with type's parameters
-------------------------------------
What if we set an IP address instead of a domain name?
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_030/config/02/config.yml
:language: yaml
:caption: A domain name in the :file:`config/02/config.yml` user data setting with an IP address
..
---
manual:
http_proxy:
address: 19.168.230.51
:tutorial:`Download this file from the rougail-tutorials git repository <src/tag/v1.1_030/config/02/config.yml>`
With a value that *is not a domain name* but an IP address, then when we will launch the Rougail CLI
we will see a little problem:
.. raw:: html
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_030/config/02/cmd_invalid.txt
:class: terminal
..
rougail -m firefox/ -u yaml -yf config/02/config.yml --cli.invalid_user_datas_error
we then have this output:
.. raw:: html
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_030/config/02/output_invalid.html
:class: error
..
<pre><span style="font-weight: bold; color: #ff0000">🛑 ERRORS</span>
<span style="color: #ff0000">┗━━ </span>the value "192.168.0.1" is an invalid domain name for
<span style="color: #ff0000"> </span>"manual.http_proxy.address" (HTTP address), must not be an IP, it will be
<span style="color: #ff0000"> </span>ignored when loading from the YAML file "config/02/config.yml"
</pre>
We observe that an error has been raised because an IP address is not a domain name.
Therefore, a type validation is taking place because we declared the type `domainname`.
.. questions:: Question
OK I agree with the `domainname` necessary type validation, but what if I want to specify
an IP address as a user value for this `address` variable?
Because it is therefore simply impossible to do so now.
Is there a way for my `address` variable to accept an IP address?
Well, it is possible to configure the type so that it accepts IP addresses.
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.
.. type-along:: For those who follow the tutorial with the help of the git repository
Now you need to checkout the `v1.1_031` version::
git switch --detach v1.1_031
Let's add a type parameter named `allow_ip`:
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_031/firefox/10-manual.yml
:language: yaml
:caption: The `allow_ip` type parameter set in the :file:`firefox/10-manual.yml` structure file
:linenos:
..
---
manual:
description: Manual proxy configuration
http_proxy:
description: HTTP Proxy
address:
description: HTTP address
type: domainname
params:
allow_ip: true
The params allow the domain name `address` variable to be set with IPs.
.. glossary::
type parameter
A type parameter is a parameter of a variable that can refine its behavior.
It is declared by adding the `params` attribute in the variable's
definition.
Now we will test with an IP address as the value for our `address` variable.
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_031/config/02/config.yml
:language: yaml
:caption: An IP address as a value in the :file:`config/02/config.yml` user value
..
---
manual:
http_proxy:
address: 192.168.0.1
:tutorial:`Download this file from the rougail-tutorials git repository <src/tag/v1.1_031/config/02/config.yml>`
if we launch the Rougail CLI on it:
.. raw:: html
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_031/config/02/cmd_ro.txt
:class: terminal
..
rougail -m firefox/ -u yaml -yf config/02/config.yml
We have this output:
.. raw:: html
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_031/config/02/output_ro.html
:class: output
We can see that the IP address value has been accepted.
A variable with type `port`
------------------------------
.. type-along:: For those who follow the tutorial with the help of the git repository
Now you need to checkout the `v1.1_032` version::
git switch --detach v1.1_032
After the `address` variable let's add, according to our use case, a new variable of type `port`:
.. image:: images/firefox_port.png
Our structure file looks like this:
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_032/firefox/10-manual.yml
:language: yaml
:caption: The `port` type variable in the :file:`firefox/10-manual.yml` structure file
:linenos:
..
port:
description: HTTP Port
type: port
default: 8080
:tutorial:`Download this file from the rougail-tutorials git repository <src/tag/v1.1_032/firefox/10-manual.yml>`
Let's assign a value to this port:
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/commit/v1.1_032/config/02/config.yml
:language: yaml
:caption: A user data :file:`config/02/config.yml` setting a value to the port variable
..
proxy_mode: Manual proxy configuration
manual:
http_proxy:
address: example.net
port: 3128
:tutorial:`Download this file from the rougail-tutorials git repository <src/tag/v1.1_032/config/02/config.yml>`
If we launch the Rougail CLI:
.. raw:: html
:class: terminal
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/commit/v1.1_032/config/02/cmd_ro.txt
We have this output:
.. raw:: html
:class: output
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/commit/v1.1_032/config/02/output_ro.html
..
<pre>╭─────────────────────────── Caption ────────────────────────────╮
│ Variable <span style="color: #00aa00">Modified value</span> │
│ <span style="color: #ff0000">Undocumented but modified variable</span> (⏳ Original default value) │
╰────────────────────────────────────────────────────────────────╯
Variables:
<span style="color: #5c5cff">┣━━ </span>📓 <span style="color: #ff0000">Configure Proxy Access to the Internet</span>: <span style="color: #00aa00">Manual proxy configuration</span> ◀
<span style="color: #5c5cff">┃ </span>loaded from the YAML file "config/02/config.yml" (⏳ No proxy)
<span style="color: #5c5cff">┗━━ </span>📂 Manual proxy configuration
<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>📓 <span style="color: #ff0000">HTTP address</span>: <span style="color: #00aa00">example.net</span> ◀ loaded from the YAML file
<span style="color: #5c5cff"> </span><span style="color: #5c5cff"> </span><span style="color: #5c5cff">┃ </span>"config/02/config.yml"
<span style="color: #5c5cff"> </span><span style="color: #5c5cff"> </span><span style="color: #5c5cff">┗━━ </span>📓 <span style="color: #ff0000">HTTP Port</span>: <span style="color: #00aa00">3128</span> ◀ loaded from the YAML file
<span style="color: #5c5cff"> </span><span style="color: #5c5cff"> </span><span style="color: #5c5cff"> </span>"config/02/config.yml" (⏳ 8080)
</pre>
How can we know what validations the port type performs on the value assigned to the variable?
There are a number of validations that are carried out with this `port` type.
Now let's assign a value that is outside the allowed ports:
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/commit/v1.1_032/config/03/config.yml
:language: yaml
:caption: A user value in :file:`config/03/config.yml` that is not allowed
:tutorial:`Download this file from the rougail-tutorials git repository <src/tag/v1.1_032/config/03/config.yml>`
Again, we launch the Rougail CLI:
.. raw:: html
:class: terminal
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/commit/v1.1_032/config/03/cmd_invalid.txt
.. rougail -m firefox/ -u yaml -yf config/03/config.yml --cli.invalid_user_datas_error
And we have this output:
.. raw:: html
:class: error
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/commit/v1.1_032/config/03/output_invalid.html
..
<pre><span style="font-weight: bold; color: #ff0000">🛑 ERRORS</span>
<span style="color: #ff0000">┗━━ </span>the value "100000" is an invalid port for "manual.http_proxy.port" (HTTP
<span style="color: #ff0000"> </span>Port), must be between 1 and 65535, it will be ignored when loading from the
<span style="color: #ff0000"> </span>YAML file "config/03/config.yml"
</pre>
We observe that, as with the `domainname` type, a number of validations are performed
to ensure that the value assigned to this variable conforms to the `port` type.
.. _boolean_variable:
A variable with type `boolean`
-----------------------------------
.. type-along:: For those who follow the tutorial with the help of the git repository
Now you need to checkout the `v1.1_033` version::
git switch --detach v1.1_033
Let's add one more variable in the `manual` family, with a much more basic type: `boolean`.
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_033/firefox/20-manual.yml
:language: yaml
:caption: A new structure file :file:`firefox/20-manual.yml` with one variable
..
%YAML 1.2
---
version: 1.1
manual:
use_for_https: true # Also use this proxy for HTTPS
...
:tutorial:`Download this file from the rougail-tutorials git repository <src/tag/v1.1_033/firefox/20-manual.yml>`
.. note::
- it is not necessary to declare the variable as a boolean type,
the type is *inferred* by the presence of the `true` default value
- we have decided to create a new structure file :file:`firefox/20-manual.yml`.
This is not necessary but usefull, please have a look at the :ref:`structure file organization and naming conventions <namingconvention>`
- here we reuse the `manual` existing family name that has already been declared in the :file:`firefox/10-manual.yml` structure file.
The content in the :file:`firefox/20-manual.yml` will be added to the existing `manual` family.
Note that if two different structure files are loaded by Rougail and if they declare the same family name,
then **the declarations are concatenated in the family name**.
Let's switch this boolean variable to a `false` value, to do this we will add
this :file:`config/02/config.yml` user data file in the :term:`configuration`:
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_033/config/02/config.yml
:language: yaml
:caption: A :file:`config/02/config.yml` user data file with false as a value for `use_for_https`
..
---
manual:
http_proxy:
address: example.net
use_for_https: false
:tutorial:`Download this file from the rougail-tutorials git repository <src/tag/v1.1_033/config/02/config.yml>`
Let's run the Rougail CLI:
.. raw:: html
:class: terminal
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_033/config/02/cmd_ro.txt
..
rougail -m firefox/ -u yaml -yf config/02/config.yml
the result is:
.. raw:: html
:class: output
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_033/config/02/output_ro.html
We could of course perform several tests on type validators, but here the validation is simple: only two values are allowed (`true` or `false`).
.. keypoints:: let's review the key points
- we can assign a `domainname` type to a variable
- we can set a :term:`type parameter` to a `domainname` variable to refine their typing behavior
- we can assign a `port` type to a variable
- we know how to set a `boolean` type variable to `true` or `false`

400
docs/tutorial/dynfam.rst Normal file
View file

@ -0,0 +1,400 @@
.. _dynfam:
A dynamically built family
==========================
.. objectives:: Objectives
In this section we will learn how to create a dynamically built family.
In a dynamically built family, instead of duplicating the definition of
identical variables in several families, they can be generated automatically.
.. prerequisites:: Prerequisites
- We assume that Rougail's library is :ref:`installed <installation>` on your computer.
- It is possible to retrieve the current state of the various Rougail files manipulated in this tutorial step
by checking out the corresponding tag of the `rougail-tutorials` git repository.
Each tag corresponds to a stage of progress in the tutorial.
Of course, you can also decide to copy/paste or download the tutorial files contents while following the tutorial steps.
If you want to follow this tutorial with the help of the corresponding :tutorial:`rougail-tutorials git repository <src/branch/1.1>`,
this workshop page corresponds to the tags :tutorial:`v1.1_060 <src/tag/v1.1_060>` to :tutorial:`v1.1_061 <src/tag/v1.1_061>`
in the repository.
::
git clone https://forge.cloud.silique.fr/stove/rougail-tutorials.git
git switch --detach v1.1_060
We handled the HTTPS mode in the previous section. But there's more modes to handle.
Let's turn back to the firefox's configuration page:
.. image:: images/soksv5.png
We see that we need to handle the SOCKS configuration in addition to the HTTPS configuration.
Moreover, we can see that these two groups of variables (also known as family) are similar in the structure:
they both have a host and a port.
There are two proxies that are to be configured :
- the HTTPS proxy
- the SOCKS proxy
As they have the same structure, would it be possible to define the two of them
in one shot?
.. note:: It's not the place here to describe what the HTTP and SOCKS protocols are.
The interesting point here is that they are very similar in our firefox's
configuration and that we can do batch processing.
.. index:: dynamically built family
Family: A dynamically built family
-------------------------------------
With Rougail, it is possible to create some kind of a model of family.
Kind of a generic family declaration.
We call this generic family creation process a dynamic creation because as we will see below,
these families exist at the very moment we define their **identifiers**.
First, here is what we need to make (without identifiers):
.. code-block:: yaml
https_proxy:
description: HTTPS Proxy
...
address:
description: HTTPS address
...
port:
description: HTTPS Port
...
sock_proxy:
description: SOCKS Proxy
...
address:
description: SOCKS address
...
port:
description: SOCKS Port
...
Now with identifiers, we have the ability to declare our families this way:
.. code-block:: yaml
"{{ identifier }}_proxy":
description: "{{ identifier }} Proxy"
dynamic:
- HTTPS
- SOCKS
...
address:
description: "{{ identifier }} address"
...
port:
description: "{{ identifier }} port"
...
.. index:: identifier
.. type-along:: What is exactly an identifier?
If you used the YAML declaration tool named `Ansible <https://docs.ansible.com>`_,
the variable used to iterate over multiple values in a task is called an **`item`**.
We call it an identifier.
It is a symbol used in the context of a loop. For example:
.. code-block:: yaml
:caption: A code example of an ansible loop
- name: Loop example with 'item'
ansible.builtin.debug:
msg: "The current value is {{ item }}"
loop:
- value1
- value2
- value3
This code will output:
.. code-block:: text
The current value is value1
The current value is value2
The current value is value3
In the Rougail context, we name this item an identifier because it is an item
that allow us to define dynamically family names.
.. glossary::
identifier
In the :ref:`dynamically built family creation field <dynfam>` we call an identifier
an item that defines a family name. An item is a variable on which an iteration
on keywords will be carried out.
An :term:`identifier` is a local variable, used only for creating multiple
iterations, used for creating multiple families in only one declaration.
It allows us to declare very similar families in a more generic way.
Here is the syntax we are using that allows the declaration of multiple families at one time:
.. code-block:: yaml
"{{ identifier }}_proxy":
description: "{{ identifier }} Proxy"
dynamic:
- HTTPS
- SOCKS
This identifier is a parameter that enables us to create two families named `https_proxy` and `socks_proxy`:
.. code-block:: yaml
https_proxy:
description: "HTTPS Proxy"
socks_proxy:
description: "SOCKS Proxy"
.. attention:: Be careful when choosing your identifiers items: pay attention that the family
names that will be dynamically created have not been declared before in some other
YAML structure file.
Here the family name is: `"{{ identifier }}_proxy"`.
If you define a dynamically built family with the `https` identifer that will
build a `https_proxy` family and if this familiy already exists,
then rougail will raise a family/variable override warning.
When choosing a dynamically built family name, rougail will replace spaces, accents, uppercases...
by valid character and put all in lowercase. Only ASCII and underscore ("`_`") characters are allowed.
As you can see here, the identifier is `HTTPS`, but the name clearly contains `https` (in lowercase).
Here is our dynamically built familiy in situation in the :file:`firefox/20-manual.yml` structure file.
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/commit/v1.1_060/firefox/20-manual.yml
:language: yaml
:caption: The :file:`firefox/20-manual.yml` structure file with the dynamically built families
..
%YAML 1.2
---
version: 1.1
manual:
use_for_https: true # Also use this proxy for HTTPS
'{{ identifier }}_proxy':
description: '{{ identifier }} Proxy'
hidden:
variable: _.use_for_https
dynamic:
- HTTPS
- SOCKS
address:
description: '{{ identifier }} address'
default:
variable: __.http_proxy.address
port:
description: '{{ identifier }} port'
default:
variable: __.http_proxy.port
...
Here is the user data file on which we will launch the Rougail CLI:
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_060/config/01/config.yml
:language: yaml
:caption: The :file:`config/01/config.yml` user data file
..
---
proxy_mode: Manual proxy configuration
manual:
http_proxy:
address: http.proxy.net
port: 3128
use_for_https: false
https_proxy:
address: https.proxy.net
.. raw:: html
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_060/config/01/cmd_ro.txt
:class: terminal
..
rougail -m firefox/ -u yaml -yf config/01/config.yml
.. raw:: html
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_060/config/01/output_ro.html
:class: output
..
rougail -m firefox/proxy.yml -u yaml --yaml.filename config/proxy.yml
╭─────────────────── Caption ────────────────────╮
│ Variable Default value │
│ Unmodifiable variable Modified value │
│ (Original default value) │
╰────────────────────────────────────────────────╯
Variables:
┗━━ 📂 Manual proxy configuration
┣━━ 📂 HTTP Proxy
┃ ┣━━ 📓 HTTP address: ... (loaded from the YAML file "userdata/proxy.yml")
┃ ┗━━ 📓 HTTP Port: ... (8080 - loaded from the YAML file "userdata/proxy.yml")
┣━━ 📓 Also use this proxy for HTTPS: true
┣━━ 📂 HTTPS Proxy
┃ ┣━━ 📓 HTTPS address: ...
┃ ┗━━ 📓 HTTPS port: ...
┗━━ 📂 SOCKS Proxy
┣━━ 📓 SOCKS address: ...
┗━━ 📓 SOCKS port: ...
We can see that the dynamically built families that have been created:
- an `HTTPS Proxy` family
- a `SOCKS Proxy` family
and, as we expected, containing an address and a port.
A conditional disabled variable with dynamic identifier
--------------------------------------------------------
.. type-along:: For those who follow the tutorial with the help of the git repository
Now you need to checkout the `v1.1_061` version::
git switch --detach v1.1_061
Here is the final version of the HTTPS and SOCKS structure file, we have added
a new variable named `version`, with the `choice` type, with a default value,
and which has the `disabled` property if the :term:`configuration` is in the `SOCKS` situation.
We will look at this in more detail below.
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_061/firefox/20-manual.yml
:language: yaml
:caption: The final :file:`firefox/20-proxy.yml` structure file
..
%YAML 1.2
---
version: 1.1
manual:
use_for_https: true # Also use this proxy for HTTPS
'{{ identifier }}_proxy':
description: '{{ identifier }} Proxy'
hidden:
variable: _.use_for_https
dynamic:
- HTTPS
- SOCKS
address:
description: '{{ identifier }} address'
default:
variable: __.http_proxy.address
port:
description: '{{ identifier }} port'
default:
variable: __.http_proxy.port
version:
description: SOCKS host version used by proxy
choices:
- v4
- v5
default: v5
disabled:
type: identifier
when: HTTPS
...
The disabled `property` is assigned here to the `version` variable
in the case where the identifier is `HTTPS`.
This means that when the current dynamically built family is determined by the identifier `HTTPS`, the variable `version` is disabled
(therefore considered as non-existent) and when the identifier is `SOCKS` the variable is present (it is accessible).
..
version:
description: SOCKS host version used by proxy
choices:
- v4
- v5
default: v5
disabled:
type: identifier
when: HTTPS
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_061/config/01/config.yml
:language: yaml
:caption: The :file:`config/01/config.yml` user data file
..
---
proxy_mode: Manual proxy configuration
manual:
http_proxy:
address: http.proxy.net
port: 3128
use_for_https: false
https_proxy:
address: https.proxy.net
.. raw:: html
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_061/config/01/cmd_ro.txt
:class: terminal
..
rougail -m firefox/ -u yaml -yf config/01/config.yml
The Rougail CLI outputs this:
.. raw:: html
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_061/config/01/output_ro.html
:class: output
..
Variables:
┣━━ 📓 Configure Proxy Access to the Internet: Manual proxy configuration ◀ loaded from the YAML file "config/01/config.yml" (⏳ No proxy)
┗━━ 📂 Manual proxy configuration
┣━━ 📂 HTTP Proxy
┃ ┣━━ 📓 HTTP address: http.proxy.net ◀ loaded from the YAML file "config/01/config.yml"
┃ ┗━━ 📓 HTTP Port: 3128 ◀ loaded from the YAML file "config/01/config.yml" (⏳ 8080)
┣━━ 📓 Also use this proxy for HTTPS: false ◀ loaded from the YAML file "config/01/config.yml" (⏳ true)
┣━━ 📂 HTTPS Proxy
┃ ┣━━ 📓 HTTPS address: https.proxy.net ◀ loaded from the YAML file "config/01/config.yml" (⏳ http.proxy.net)
┃ ┗━━ 📓 HTTPS port: 3128
┗━━ 📂 SOCKS Proxy
┣━━ 📓 SOCKS address: http.proxy.net
┣━━ 📓 SOCKS port: 3128
┗━━ 📓 SOCKS host version used by proxy: v5
.. keypoints:: Key points
- We now know how to declare a dynamically built family, with setting its identifier.
- we have a new use case for the `disabled` property.
We know how to disable a dynamically built variable based on a familiy identifier.

268
docs/tutorial/family.rst Normal file
View file

@ -0,0 +1,268 @@
Group variables inside families
=================================
.. 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:: Prerequisites
- We assume that Rougail's library is :ref:`installed <installation>` on your computer.
- It is possible to retrieve the current state of the various Rougail files manipulated in this tutorial step
by checking out the corresponding tag of the `rougail-tutorials` git repository.
Each tag corresponds to a stage of progress in the tutorial.
Of course, you can also decide to copy/paste or download the tutorial files contents while following the tutorial steps.
If you want to follow this tutorial with the help of the corresponding :tutorial:`rougail-tutorials git repository <src/branch/1.1>`,
this workshop page corresponds to the tags :tutorial:`v1.1_020 <src/tag/v1.1_020>` to :tutorial:`v1.1_022 <src/tag/v1.1_022>`
in the repository.
::
git clone https://forge.cloud.silique.fr/stove/rougail-tutorials.git
git switch --detach v1.1_020
.. type-along:: Let's recap how far we've come
We have this choice variable in its structure definition file:
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_010/firefox/00-proxy.yml
:linenos:
:language: yaml
:caption: The `proxy_mode` choice variable in the :file:`firefox/00-proxy.yml` structure file
..
---
proxy_mode:
description: Configure Proxy Access to the Internet
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`.
In short, let's describe our `proxy_mode` variable like this:
.. confval:: proxy_mode
:type: `choice`
:default: No proxy
Proxy mode's settings
Now we will define new variables, and other structure definitions.
For the sake of clarity, we will put the structure definitions in separate files.
Please have a look at the :ref:`file naming and organizing convention <namingconvention>`.
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`::
.
└── firefox
├── 00-proxy.yml
└── 10-manual.yml
Creating a new family
-----------------------
Let's create a family named `manual` which obviously corresponds to the proxy's manual configuration choice.
.. 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*
which means that this family is a container variable that contains no variable yet.
.. warning::
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`.
We have to force the family type inference.
It's because we don't have set any :term:`variable` inside yet. When we will have a variable inside of this family,
we will make a YAML block (to create a block in YAML, you just need to indent the lines) and the Rougail's type inference engine will implicitely infer the variable's container as a family type.
Or a sub family
----------------
.. type-along:: For those who follow the tutorial with the help of the git repository
Now you need to checkout the `v1.1_021` version::
git switch --detach v1.1_021
.. glossary::
sub family
A sub family is a family inside a family.
Creating a family hierarchy of families (family inside a family) is very easy:
.. 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
:tutorial:`Download this file from the rougail-tutorials git repository <src/tag/v1.1_021/firefox/00-proxy.yml>`
Here in our use case we used the :term:`short-hand declaration mode <short-hand notation>`
to declare our `manual` family:
.. code-block:: yaml
manual: # Manual proxy configuration
And the `http_proxy` family lives inside of this `manual` family.
We therefore created a hierarchy of families.
Putting a variable inside of a family or a sub family
-----------------------------------------------------------
.. type-along:: For those who follow the tutorial with the help of the git repository
Now you need to checkout the `v1.1_022` version::
git switch --detach v1.1_022
We are going to put a variable inside of a family or a sub family
Let's create a variable in the `http_proxy` family.
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_022/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
:tutorial:`Download this file from the rougail-tutorials git repository <src/tag/v1.1_022/firefox/10-manual.yml>`
Now that the :confval:`address` variable is declared, the :term:`operator` can set :term:`a value <value>` to it.
In short, let's describe our `address` variable like this:
.. confval:: address
:default: None
This is the HTTP address of the proxy
We have reached the definition of the address in the `http_proxy` family; there will be other variables to define in this family.
.. image:: images/firefox_manual_family.png
.. type-along:: 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_022/config/01/output_ro.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>
.. type-along:: Let's set user values in a user data file
Here is a user data file sample:
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_022/config/02/config.yml
:language: yaml
:caption: A user file named :file:`config/03/config.yml` with a value set for the `address` variable
:name: RougailAddresseVariableUserValue
..
---
proxy_mode: Manual proxy configuration
manual:
http_proxy:
address: example.net
:tutorial:`Download this file from the rougail-tutorials git repository <src/tag/v1.1_022/config/02/config.yml>`
Let's validate the consitency of the :term:`configuration`:
.. raw:: html
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_022/config/02/cmd_ro.txt
:class: terminal
..
rougail -m firefox/ -u yaml -yf config/02/config.yml
Everything is OK:
.. raw:: html
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_022/config/02/output_ro.html
:class: output
..
<pre>╭──────── Caption ────────╮
│ Variable <span style="color: #ffd700">Default value</span> │
│ Modified value │
╰─────────────────────────╯
Variables:
<span style="color: #5c5cff">┣━━ </span>📓 Configure Proxy Access to the Internet: <span style="color: #ffd700">No proxy</span>
<span style="color: #5c5cff">┗━━ </span>📂 Manual proxy configuration
<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>📓 HTTP address: example.net ◀ loaded from the YAML file "config/02/config.yml"
</pre>
Let's recap about the user data. We can see in this Rougail CLI output that:
- the `proxy_mode` value is set by default by the :term:`integrator`
- the `address` value is has been set by an :term:`operator`
.. keypoints:: Let's review the key points
**Keywords**
- we know how to define :term:`variable`\ s inside of a family
- we now know what a :term:`mandatory` variable is and why it is necessary to assign values to the variables
- we kwow how to set a variable's :term:`user value <user data>`
- we have the big picture : the :term:`configuration`, which is (the structure files + the user data files)
**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`.

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

View file

Before

Width:  |  Height:  |  Size: 123 KiB

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

View file

Before

Width:  |  Height:  |  Size: 154 KiB

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

58
docs/tutorial/index.rst Normal file
View file

@ -0,0 +1,58 @@
.. _tutorial:
Tutorial with a real world sample
=====================================
Here is a fairly complete tutorial, it 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:: 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 use case.
.. attention:: We are not coding a Firefox plugin here.
We are just going to handle some of the Firefox configuration settings
with Rougail. We are just validating them.
The configuration option values entered by the user have to be:
- validated
- consitent
- conform
Let's dive into this **configuration options validation** use case.
.. prerequisites:: Important advice
It is advisable to follow this tutorial with the help of the corresponding :tutorial:`Rougail git repository tutorial <src/branch/1.1>`.
You can instead copy/paste or download the different file contents that are explained in this tutorial step and save the files to your computer.
However, if you use the git Rougail tutorial repository, you will have all the necessary files distributed in the correct tree structure,
which is in our opinion much more practical.
::
git clone https://forge.cloud.silique.fr/stove/rougail-tutorials/src/branch/1.1
.. toctree::
:titlesonly:
:caption: The Firefox tutorial
preliminary
choice
family
domainname
calculated
disabled
dynfam
jinja

328
docs/tutorial/jinja.rst Normal file
View file

@ -0,0 +1,328 @@
Playing with Jinja
====================
.. objectives:: Objectives
In this section we will learn how to create new ways of calculation.
Up to now, our only way of dynamically (that is, during the runtime) calculating
a value is to point on another variable's value. But this is not the only way.
.. prerequisites:: Prerequisites
- We assume that Rougail's library is :ref:`installed <installation>` on your computer.
- It is possible to retrieve the current state of the various Rougail files manipulated in this tutorial step
by checking out the corresponding tag of the `rougail-tutorials` git repository.
Each tag corresponds to a stage of progress in the tutorial.
Of course, you can also decide to copy/paste or download the tutorial files contents while following the tutorial steps.
If you want to follow this tutorial with the help of the corresponding :tutorial:`rougail-tutorials git repository <src/branch/1.1>`,
this workshop page corresponds to the tags :tutorial:`v1.1_070 <src/tag/v1.1_070>` to :tutorial:`v1.1_072 <src/tag/v1.1_072>`
in the repository.
::
git clone https://forge.cloud.silique.fr/stove/rougail-tutorials.git
git switch --detach v1.1_070
A conditional hidden family with Jinja
---------------------------------------
GWEN
We can hide or disable some variables or families with other techniques than
pointing on a variable's value.
Let's reason on the previous HTTPS proxy configuration's manual mode:
.. code-block:: yaml
manual:
use_for_https:
description: Also use this proxy for HTTPS
default: true
https_proxy:
type: family
description: HTTPS Proxy
hidden:
variable: manual.use_for_https
This is extracted from the proxy's manual configuration we discussed before.
We see here that there is an `https_proxy` family that is going to be hidden
depending on the value of another variable:
.. code-block:: yaml
https_proxy:
type: family
hidden:
variable: manual.use_for_https
Now we could write it like that:
.. code-block:: yaml
manual:
use_for_https:
description: Also use this proxy for HTTPS
default: true
https_proxy:
description: HTTPS Proxy
type: family
hidden:
type: jinja
jinja: |
{% if rougail.manual.use_for_https %}
the HTTPS Proxy family is hidden
{% endif %}
Yes, it's done in a more complicated (but more powerful) way.
Let's explain this a little:
We have replaced this simple hidden property declaration:
.. code-block:: yaml
hidden:
variable: manual.use_for_https
by this (more complicated) hidden property declaration:
.. code-block:: yaml
hidden:
type: jinja
jinja: |
{% if rougail.manual.use_for_https %}
the HTTPS Proxy family is hidden
{% endif %}
The fact is that it has same result, but here we have more possibilities.
The hidden process is done by a calculation.
Another jinja calculation type sample
---------------------------------------
We can now hide or disable some variables or families with other techniques than
pointing on a variable's value.
Let's reason upon the proxy's manual configuration we discussed before.
We have the :file:`dict/02-proxy_manual.yml` structure file:
.. code-block:: yaml
:caption: the :file:`structfile/02-proxy_manual.yml` file
---
version: '1.1'
manual:
description: Manual proxy configuration
type: family
disabled:
type: jinja
jinja: |
{% if rougail.proxy.proxy_mode != 'Manual proxy configuration' %}
the proxy mode is not manual
{% endif %}
.. questions:: Question
**question**: OK then. What happens when you select the "Manual proxy configuration"?
Here if the user selects the "Manual proxy configuration" proxy mode,
the the `manual` family will be disabled. This is what the jinja code says.
Let's explain it more precisely.
.. note:: The "the proxy mode is not manual" output is be used in the log outputs
for example while
Why Jinja?
---------------
.. questions:: What about this `Jinja` type?
If the :term:`Jinja` template returns some text, then the family will be `disabled`. Otherwise it is accessible.
Deactivating a family means that we will not be able to access it as well as the variables or families included in this family.
.. note:: If the Jinja template does not return any text, the variable will be **enabled**.
Here we are using the Jinja condition statement.
.. glossary::
Jinja
`Jinja <https://jinja.palletsprojects.com>`_ is a template engine.
we are using Jinja in a classical way, that is, Jinja allows us to handle different cases,
for example with the `if` statement.
What can be calculated?
---------------------------
We have seen that the `disabled` or `hidden` properties could be calculated.
The default values can be calculated too.
.. todo:: montrer un exemple de valeur par défaut calculées (type jinja)
.. todo:: montrer aussi ici des exemples de calculs de valeurs variables, ce qui est un des usages principaux de jinja
Using jinja in a dynamic family declaration
-----------------------------------------------
Let's come back to the previous section's :ref:`dynamic family example <conditional_hidden_family>`\ .
In a dynamic family, as seen before, you have the possibility to name your identifier. In the classic declaration,
the identifier's variable is named "identifier" by default. Sounds logical:
.. code-block:: yaml
"{{ identifier }}_proxy":
description: "{{ identifier }} Proxy"
dynamic:
- HTTPS
- SOCKS
Here the identifer's variable takes the value of the `dynamic` family parameter.
.. type-along:: Using a jinja calculation with a parameter
We have the possibility to use a given variable variable inside a jinja calculation:
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_050/firefox/20-manual.yml
:language: yaml
:caption: firefox/20-proxy.yml
..
manual:
use_for_https:
description: Also use this proxy for HTTPS
default: true
"{{ identifier }}_proxy":
description: "{{ identifier }} Proxy"
dynamic:
- HTTPS
- SOCKS
hidden:
jinja: |
{% if my_identifier == 'HTTPS' and manual.use_for_https %}
HTTPS is same has HTTP
{% endif %}
params:
my_identifier:
type: identifier
description: |
in HTTPS case if "manual.use_for_https" is set to True
address:
description: "{{ identifier }} address"
default:
variable: manual.http_proxy.address
port:
description: "{{ identifier }} port"
default:
variable: manual.http_proxy.port
version:
description: SOCKS host version used by proxy
choices:
- v4
- v5
default: v5
disabled:
type: identifier
when: 'HTTPS'
This can be done by defining a `my_identifier` variable.
This jinja variable comes from the `params` parameter of the `hidden` property.
.. code-block:: yaml
params:
my_identifier:
type: identifier
Here the `hidden` property's value is defined by a jinja calculation.
In this jinja calculation we have a `my_identifier` jinja variable.
.. code-block:: yaml
hidden:
jinja: |
{% if my_identifier == 'HTTPS' and manual.use_for_https %}
HTTPS is same has HTTP
{% endif %}
params:
my_identifier:
type: identifier
description: |
in HTTPS case if "manual.use_for_https" is set to True
.. type-along:: The `when` and `when_not` parameter notation
.. todo:: ça devrait être sur une autre page. déplacer cela ailleurs
Handling the SOCKS version
----------------------------
Now we need to handle the SOCKS version as show in the firefox configuration screenshot:
.. image:: images/firefox_soks_version.png
We'll just add a choice variable with a default value and a disabled property:
.. code-block:: yaml
version:
description: SOCKS host version used by proxy
choices:
- v4
- v5
default: v5
disabled:
type: identifier
when: 'HTTPS'
There is still something new about this YAML, though. It is the `when` parameter
of the `disabled` property. You have two possible notations: `when` or `when_not`.
These two notations are just a short hand of what we could express in jinja as
this code:
.. code-block:: jinja
{% if identifier == 'HTTPS' %}
when the identifer equals 'HTTPS' then the SOCKS version is disabled
{% endif %}
And the `when_not` parameter is just the logical opposite.
Here is the final version of our YAML dynamic family:
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_050/firefox/20-manual.yml
:language: yaml
:caption: firefox/20-proxy.yml
.. keypoints:: Key points
**keywords**
- calculation with a jinja type calculation
- calculation with a `when` or `when_not` parameter
- defining a jinja internal variable in a jinja calculation
**progress**
Here we have come to the possibility of making any kind of calculations based on the state of the :term:`configuration`.
This is an important feature to manage the stateful aspect of a configuration.

View file

@ -0,0 +1,419 @@
Getting started
====================
Presentation of the firefox configuration options
--------------------------------------------------
At first glance we can see that we have a selection of five configuration options that we need to fill in, they are highlighted here in this screenshot:
.. image:: images/firefox_01.png
We'll learn in this tutorial how to set the values of the configuration options in a clean way with the Rougail library.
.. objectives:: Objectives of this section
We will learn how to:
- create a :term:`structure description file <structure file>`
- add a :term:`structure file <structure file>` format version in the structure file
- add a :term:`variable` in the structure file and set its default :term:`value`
.. prerequisites:: Prerequisites
- We assume that Rougail's library is :ref:`installed <installation>` on your computer.
- It is possible to retrieve the current state of the various Rougail files manipulated in this tutorial step
by checking out the corresponding tag of the `rougail-tutorials` git repository.
Each tag corresponds to a stage of progress in the tutorial.
Of course, you can also decide to copy/paste or download the tutorial files contents while following the tutorial steps.
If you want to follow this tutorial with the help of the corresponding :tutorial:`rougail-tutorials git repository <src/branch/1.1>`,
this workshop page corresponds to the tags :tutorial:`v1.1_000 <src/tag/v1.1_000>` to :tutorial:`v1.1_003 <src/tag/v1.1_003>`
in the repository:
::
git clone https://forge.cloud.silique.fr/stove/rougail-tutorials.git
git switch --detach v1.1_000
Creating a structure file
--------------------------
.. demo:: The folder structure
Here is the tree structure we want to have::
rougail-tutorials
└── firefox
└── 00-proxy.yml
- Let's make a :file:`rougail-tutorials` directory, with a :file:`firefox` subfolder.
- First, we will create a :term:`structure file <structure file>`, so let's create a :file:`00-proxy.yml` file
located in the :file:`firefox` subfolder.
This is an empty Rougail :term:`structure description file: <structure file>`
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_000/firefox/00-proxy.yml
:language: yaml
:caption: An empty Rougail structure file with only the YAML header and the version number
:name: RougailStructVersion
..
---
version: 1.1
:tutorial:`Download this file from the rougail-tutorials git repository <src/tag/v1.1_000/firefox/00-proxy.yml>`
This `version` specification is just the Rougail YAML's format version specification.
YAML files are loaded using a version 1.2 file parser. It is recommended (but not mandatory) to specify this in the file header, especially for linters.
By now, we have an empty structure file with the format specification in it.
Let's add our first variable
------------------------------
.. type-along:: For those who follow the tutorial with the help of the git repository
Now you need to checkout the `v1.1_001` version::
git switch --detach v1.1_001
- A variable is defined at a minimum by its name.
- A :term:`variable` lives in the :term:`structure description file <structure file>`.
Here we define a variable named `proxy_mode`:
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_001/firefox/00-proxy.yml
:language: yaml
:caption: A Rougail structure file with only one variable in it
:name: RougailDictionaryFirstVariableName
..
---
proxy_mode:
:tutorial:`Download this file from the rougail-tutorials git repository <src/tag/v1.1_001/firefox/00-proxy.yml>`
Let's run the Rougail CLI utility command in a terminal:
.. code-block:: text
:class: terminal
rougail -m firefox/
Well, we notice that we have an error:
.. raw:: html
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_001/config/01/output_ro.html
:class: error-box
..
🛑 ERRORS
┣━━ The following variables are mandatory but have no value:
┗━━ - proxy_mode
It's because this first defined variable is :term:`mandatory` and needs to have a value set **but** there's no value yet.
We can therefore deduce the fact that:
.. admonition:: Fact
Once defined, an option configuration :term:`value` is :term:`mandatory` by default.
That is to say, it is absolutely necessary to assign a value to this variable.
Rougail expects the `proxy_mode` configuration option's value to be set.
.. glossary::
mandatory
A variable is mandatory when a value is required, that is, `None` **is not** a possible value.
It **must** have a defined value.
.. seealso:: To go further, have a look at the :tiramisu:`mandatory option <glossary.html#term-mandatory-option>`
according to the :xref:`Tiramisu <tiramisu>` underlyning consistency system.
You will learn that it is actually possible to disable the mandatory property behavior,
but you need to declare it explicitely.
Describe the variable
-------------------------
Let's add a variable's description, which is not mandatory but which is usually a good practice.
.. type-along:: For those who follow the tutorial with the help of the git repository
Now you need to checkout the `v1.1_002` version::
git switch --detach v1.1_002
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_002/firefox/00-proxy.yml
:language: yaml
:caption: A Rougail structure file with a variable and a description
:name: RougailStructFirstVariableDescription
..
---
proxy_mode: # Configure Proxy Access to the Internet
:tutorial:`Download this file from the rougail-tutorials git repository <src/tag/v1.1_002/firefox/00-proxy.yml>`
You have two way to define a variable's description:
- the verbose way:
.. code-block:: yaml
proxy_mode:
description: Configure Proxy Access to the Internet
- or a short-hand way, setting the description using the "`#`" YAML comment notation:
.. code-block:: yaml
proxy_mode: # Configure Proxy Access to the Internet
If we launch the Rougail CLI command:
.. raw:: html
:class: terminal
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_002/config/01/cmd_rw.txt
we have this output:
.. raw:: html
:class: output
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_002/config/01/output_rw.html
..
<pre>╭──────────────────── Caption ─────────────────────╮
│ <span style="color: #ff0000">Undocumented but modified variable</span> <span style="color: #ffd700">Default value</span> │
╰──────────────────────────────────────────────────╯
Variables:
<span style="color: #5c5cff">┗━━ </span>📓 <span style="color: #ff0000">Configure Proxy Access to the Internet</span>: <span style="color: #ffd700">null</span>
</pre>
We can see here that the variable's description string "Configure Proxy Access to the Internet" is used
to refer to the `proxy_mode` variable.
.. note:: The description is used in UI tools and outputs instead of the variable name.
The goal here is to provide an explanation of the variable for the user,
not to show the technical name of the variable as defined by the :term:`integrator`.
Set a default value
---------------------
.. type-along:: For those who follow the tutorial with the help of the git repository
Now you need to checkout the `v1.1_003` version::
git switch --detach v1.1_003
We will learn different ways to set a value, the first way is setting a *default* value.
.. glossary::
default value
A default value is a variable value that is predefined, that's why this value is placed
right in the structure file.
Let's add a default value to this `proxy_mode` variable.
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_003/firefox/00-proxy.yml
:language: yaml
:caption: A Rougail structure file with a default value for the variable
:name: RougailDictionaryVariableDefault
..
---
proxy_mode: No proxy # Configure Proxy Access to the Internet
:tutorial:`Download this file from the rougail-tutorials git repository <src/tag/v1.1_003/firefox/00-proxy.yml>`
The `proxy_mode` variable requires a value, that's why we have set a `No proxy` default value.
.. raw:: html
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_003/config/01/output_ro.html
:class: output
..
<pre>╭─────── Caption ────────╮
│ Variable <span style="color: #ffd700">Default value</span> │
╰────────────────────────╯
Variables:
<span style="color: #5c5cff">┗━━ </span>📓 Configure Proxy Access to the Internet: <span style="color: #ffd700">No proxy</span>
</pre>
As we have set the `proxy_mode`'s value as `No proxy` by default,
The chosen value is indicated in the Rougail's CLI output as the default choice.
- here is the short-hand default setting and description:
.. code-block:: yaml
proxy_mode: No proxy # Configure Proxy Access to the Internet
- and there is the verbose way of setting a default value:
.. code-block:: yaml
proxy_mode:
description: Configure Proxy Access to the Internet
default: No proxy
There are some other :term:`short-hand ways <short-hand notation>` with Rougail that you may encounter
as you read the Rougail's documentation and tutorial.
.. admonition:: How to set a value -- the assignment
A default value has been set, great. This raises a question about what a normal value is.
Now then how can I assign a normal value to a variable?
.. type-along:: The different Rougail roles and setting a variable's value
So far we have only talked about the actor that writes the :term:`structure files <structure file>`\ .
The one who writes the structure file plays the *role* of the *integrator*.
.. glossary::
integrator
An integrator in the Rougail field is the person who writes the :term:`structure files <structure file>`\ .
He has the responsibility of the integration process, that is,
he 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 organisation of the variables between them.
Now we will talk about the one that defines the values. His role is called the operator role.
.. glossary::
operator
An operator in the Rougail field is the person who assigns :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.
The type validation is driven by the definitions in the :term:`structure file <structure file>`.
It is the operator's responsibility to set the user data variables values.
The operator does not handle the structure files,
he is responsible of other files called the :term:`user data files <user data file>`.
.. glossary::
user data
User datas, as opposed to structured datas, are datas that only concern the assignment of values
and not the consistency of the variables between them.
The variable's values are also called **user values**.
The consistency field is outside of the user data scope.
The consistency is handled in the :term:`structured datas <structured data>`\ 's scope.
.. important:: For now, we don't know how to disable the default `mandatory` settings,
so if neither a default value nor a user value are set for a given variable, Rougail will raise an error.
.. exercise:: Folder structure update
Now we add a user data file named :file:`config/config.yml` in our project::
rougail-tutorials
├── firefox
│ ├── 00-proxy.yml
└── config
└── config.yml
.. type-along:: How to set a user data value
If the integrator has not set any default value in his structure file,
it's up to the operator to do the job in the `config.yml` file:
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_003/config/02/config.yml
:language: yaml
:caption: A Rougail user data file :file:`config/config.yml`, with a value set.
:name: RougailConfigDefaultValue
..
---
proxy_mode: No proxy
:tutorial:`Download this file from the rougail-tutorials git repository <src/tag/v1.1_003/config/02/config.yml>`
The operator needs to add the `-u yaml -yf config/config.yml` options to the Rougail CLI:
.. raw:: html
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_003/config/02/cmd_ro.txt
:class: terminal
..
rougail -m firefox/ -u yaml -yf config/02/config.yml
which gives us this output:
.. raw:: html
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_003/config/02/output_ro.html
:class: output
..
<pre>╭──────── Caption ────────╮
│ Variable Modified value │
╰─────────────────────────╯
Variables:
<span style="color: #5c5cff">┗━━ </span>📓 proxy_mode: No proxy ◀ loaded from the YAML file "config/02/config.yml"
</pre>
Now the `proxy_mode`'s new `No proxy` value is the same as the default value but we see in the Rougail CLI output that the value
comes from the :file:`config/02/config.yml` user data file. From now on this `proxy_mode` variable's value
is a user data value and not a default value (even if it's actually the same value).
.. type-along:: Structure values and user data values
We can see with the Rougail CLI utility where the values come from.
It can come from an integrator's setting or from an operator's setting.
.. admonition:: Reminder
- the integrator works on structure files, he can define default value for variables
- the operator works on user data files, he only can set user data values for variables
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.
.. type-along:: User data files are where the user values live
We need to set the values in separate files, called `user data files`.
.. glossary::
user data file
A user data file is a file where only :term:`user data` are set.
A user file is a file where there are only user data in it, users can set values, called user values --
that is variable's values that have been set by an :term:`operator`\ .
see also :term:`user data`
.. 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.
.. keypoints:: Key points progress
**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 mandatory value
- a variable's default value
- a variable's user value
- the :term:`integrator` and :term:`operator` roles
- a :term:`configuration`

View file

@ -1,208 +1,4 @@
Tutorial: a real world sample
==============================
.. 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.1'
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.
Obviously all the variables can be put in the same file.
The proxy's configuration type
----------------------------------
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`).
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.1'
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
------------------
.. questions:: 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`.
Let's create the :file:`dict/02-proxy_manual.yml` dictionary:
.. code-block:: yaml
:caption: the the :file:`dict/02-proxy_manual.yml` file
---
version: '1.1'
proxy:
manual:
description: Manual proxy configuration
type: family
disabled:
type: jinja
jinja: |
{% if rougail.proxy.proxy_mode != 'Manual proxy configuration' %}
the proxy mode is not manual
{% endif %}
Well, if the user selects the "Manual proxy configuration" proxy mode, we want to see a new subfamily (that is, a new set of configuration variables) called `manual` to appear (which is disabled).
.. glossary::
subfamily
A subfamily is just a family inside a family, a family that contains a family.
.. questions:: What about this `Jinja` type?
If the :term:`Jinja` template returns some text, then the family will be `disabled`. Otherwise it is accessible.
Deactivating a family means that we will not be able to access it as well as the variables or families included in this family.
.. note:: If the Jinja template does not return any text, the variable will be **enabled**.
Here we are using the Jinja condition statement.
.. glossary::
Jinja
`Jinja <https://jinja.palletsprojects.com>`_ is a template engine.
we are using Jinja in a classical way, that is, Jinja allows us to handle different cases,
for example with the `if` statement.
The HTTP proxy configuration
------------------------------
In this family let's add a *subfamily* named `http_proxy`, containing the address and port configuration variables.
Let's create the :file:`dict/03-proxy_manual_http_proxy.yml` dictionary:
.. code-block:: yaml
:caption: the the :file:`dict/02-proxy_manual.yml` file
:linenos:
---
version: '1.1'
proxy:
manual:
http_proxy:
description: HTTP Proxy
address:
description: HTTP address
type: domainname
port:
description: HTTP Port
type: port
default: '8080'
Both variables `address` and `port` have particular types (respectively `domainname` line 9 and `port` line 12) to validate the values configured by the user.
.. note:: No need to specify the type of the `http_proxy` as a family type, because here we have declared variables inside of it.
Duplicating the HTTP configuration to HTTPS
---------------------------------------------
We then want to offer the user the possibility of providing the same proxy for the HTTPS requests. Let's create the :file:`dict/04-proxy_manual_http_use_for_https.yml` file:
.. code-block:: yaml
:caption: the :file:`dict/04-proxy_manual_http_use_for_https.yml` file
version: '1.1'
proxy:
manual:
use_for_https:
description: Also use this proxy for HTTPS
type: boolean
This variable is a `boolean` type, its default value is `True`.
:orphan:
HTTPS proxy configuration detail
-----------------------------------

View file

@ -0,0 +1,3 @@
Load user datas from Bitwarden server
=====================================

View file

@ -0,0 +1,3 @@
Load user datas from commandline parser
=======================================

View file

@ -0,0 +1,3 @@
Load user datas from a environment variable
===========================================

21
docs/user_datas/index.rst Normal file
View file

@ -0,0 +1,21 @@
`Rougail`'s user datas description
==================================
Rougail is a collections of subproject to adjust functionalities to your needs.
User datas is one of category of subjects. The goal is to setup variable with value define by user.
There is differents user datas types:
.. toctree::
:titlesonly:
:caption: Use library
yaml
environment
commandline
bitwarden
questionary
.. ansible

View file

@ -0,0 +1,3 @@
Load user datas from a command line interface
=============================================

3
docs/user_datas/yaml.rst Normal file
View file

@ -0,0 +1,3 @@
Load user datas from a YAML file
================================

View file

@ -1,33 +1,98 @@
The variables
===================
==============
Synopsis
------------
---------
.. glossary::
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, most 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.
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.
value
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>`.
Variable's types
-----------------
.. type-along:: type inference
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.
.. type-along:: integer type
If the operator sets an option value for example with the `integer` type, like this:
.. code-block:: yaml
---
example_var:
description: This is an example variable
type: integer
Then Rougail will expect a `int` as a value for the `example_var` variable.
.. type-along:: the choice type
.. glossary::
choice type
A choice type variable is a variable where the content is constrained by a list
When a variable's setting is "choice" (`type: choice`), it means that
there is a list of available values that can be selected.
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.
.. glossary::
short-hand notation
A short-hand notation in Rougail is the ability to define a variable in
a short-hand way, there are several example:
- a default value:
.. code-block:: yaml
my_var: true
instead of:
.. code-block:: yaml
my_var:
default: true
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.
@ -38,12 +103,16 @@ If you add a comment in the same line of the name, this comment will be used has
.. code-block:: yaml
%YAML 1.2
---
version: '1.1'
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
-------------
@ -63,7 +132,7 @@ Parameters
* - **default**
- Default value(s) of the variable.
This value is typed, you must correctly fill out the YAML file to avoid defining a value with an incorrect type. For example, a `number` must be a digit type, a multiple variable must be a `list` type, ...
This value is typed, you must correctly fill out the YAML file to avoid defining a value with an incorrect type. For example, a `integer` must be a digit type, a multiple variable must be a `list` type, ...
For a non :term:`leading` multiple variable, the first value defined in the list will also be the default value proposed if a new value is added to this variable.
@ -105,6 +174,12 @@ Parameters
- The value of the variable is a list.
**Default value**: `false`
**Parameters**:
- multi_length: number of expected values for a multiple variable
- multi_min_length: maximum number of expected values for a multiple variable
- multi_max_length: minimum number of expected values for a minimum variable
* - **unique**
`boolean`
@ -146,7 +221,7 @@ Parameters
* - **redefine**
`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`
* - **exists**
@ -154,23 +229,23 @@ Parameters
`boolean`
- 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`.
**Default value**: `null`
* - **test**
`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.
Variables types
----------------
Variables type list
--------------------
A variable **has a type**.
A variable **always has a type**. The system is **strongly** typed.
This type enables the variable to define the values that are accepted by this variable.
Depending on the definition of the variable type, the defined variable will accept values of the associated type.
.. list-table::
:widths: 15 25 20 15
@ -189,11 +264,11 @@ This type enables the variable to define the values that are accepted by this va
"1"
"true"
* - number
- a number
- `min_number`: minimum number allowed
* - integer
- a integer
- `min_integer`: minimum integer allowed
`max_number`: maximum number allowed
`max_integer`: maximum integer allowed
- 1
* - float
- a floating number

View file

@ -1,166 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ORGANIZATION
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"POT-Creation-Date: 2024-10-30 13:21+0100\n"
"PO-Revision-Date: 2024-10-30 13:39+0100\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: pygettext.py 1.5\n"
"X-Generator: Poedit 3.5\n"
#: src/rougail/annotator/family.py:142
msgid "default variable mode \"{0}\" is not a valid mode, valid modes are {1}"
msgstr ""
"le mode d'une variable par défaut \"{0}\" n'est pas un mode valide, les "
"modes valides sont {1}"
#: src/rougail/annotator/family.py:148
msgid "default family mode \"{0}\" is not a valid mode, valid modes are {1}"
msgstr ""
"le mode d'une famille par défaut \"{0}\" n'est pas un mode valide, les modes "
"valides sont {1}"
#: src/rougail/annotator/family.py:180
msgid "mode \"{0}\" for \"{1}\" is not a valid mode, valid modes are {2}"
msgstr ""
"le mode \"{0}\" pour \"{1}\" n'est pas un mode valide, les modes valides "
"sont {2}"
#: src/rougail/annotator/family.py:184
msgid "mode \"{0}\" for \"{1}\" is not a valid mode, no modes are available"
msgstr ""
"le mode \"{0}\" pour \"{1}\" n'est pas un mode valide, aucun mode ne sont "
"définis"
#: src/rougail/annotator/family.py:248
msgid ""
"the variable \"{0}\" is mandatory so in \"{1}\" mode but family has the "
"higher family mode \"{2}\""
msgstr ""
"la variable \"{0}\" est obligatoire donc dans le mode \"{1}\" mais la "
"famille a un mode supérieur \"{2}\""
#: src/rougail/annotator/family.py:286
msgid ""
"the follower \"{0}\" is in \"{1}\" mode but leader have the higher mode "
"\"{2}\""
msgstr ""
"la variable suiveuse \"{0}\" a le mode \"{1}\" mais la variable leader a un "
"mode supérieur \"{2}\""
#: src/rougail/annotator/family.py:319
msgid ""
"the family \"{0}\" is in \"{1}\" mode but variables and families inside have "
"the higher modes \"{2}\""
msgstr ""
"la famille \"{0}\" a le mode \"{1}\" mais les variables et les familles à "
"l'intérieur ont des modes supérieurs \"{2}\""
#: src/rougail/annotator/family.py:337
msgid ""
"the variable \"{0}\" is in \"{1}\" mode but family has the higher family "
"mode \"{2}\""
msgstr ""
"la variable \"{0}\" est dans le mode \"{1}\" mais la famille a le mode "
"supérieur \"{2}\""
#: src/rougail/annotator/value.py:80
msgid "the follower \"{0}\" without multi attribute can only have one value"
msgstr ""
"la variable suiveuse \"{0}\" sans l'attribut multi peut avoir seulement une "
"valeur"
#: src/rougail/annotator/value.py:96
msgid "the variable \"{0}\" is multi but has a non list default value"
msgstr ""
"la variable \"{0}\" est multiple mais a une valeur par défaut sans être une "
"liste"
#: src/rougail/annotator/variable.py:192
msgid ""
"the variable \"{0}\" has regexp attribut but has not the \"regexp\" type"
msgstr ""
"la variable \"{0}\" a un attribut regexp mais n'a pas le type \"regexp\""
#: src/rougail/annotator/variable.py:235
msgid ""
"the variable \"{0}\" has choices attribut but has not the \"choice\" type"
msgstr ""
"la variable \"{0}\" a un attribut choices mais n'a pas le type \"choice\""
#: src/rougail/annotator/variable.py:263
msgid ""
"the variable \"{0}\" has an unvalid default value \"{1}\" should be in {2}"
msgstr ""
"la variable \"{0}\" a la valeur par défaut invalide \"{1}\" devrait être {2}"
#: src/rougail/convert.py:268
msgid ""
"A variable or a family located in the \"{0}\" namespace shall not be used in "
"the \"{1}\" namespace"
msgstr ""
"Une variable ou une famille localisé dans l'espace de nom \"{0}\" ne devrait "
"pas être utilisé dans l'espace de nom \"{1}\""
#: src/rougail/convert.py:462
msgid "unknown type {0} for {1}"
msgstr "type {0} inconnu pour {1}"
#: src/rougail/convert.py:1323
msgid "duplicate dictionary file name {0}"
msgstr "nom de fichier {0} de dictionnaire dupliqué"
#: src/rougail/convert.py:1370
msgid "Cannot execute annotate multiple time"
msgstr "Ne peut exécuter l'annotation plusieurs fois"
#: src/rougail/error.py:70
msgid "{0} in {1}"
msgstr "{0} dans {1}"
#: src/rougail/structural_commandline/annotator.py:70
msgid "alternative_name \"{0}\" conflict with \"--help\""
msgstr "alternative_name \"{0}\" est en conflit avec \"--help\""
#: src/rougail/structural_commandline/annotator.py:73
msgid "conflict alternative_name \"{0}\": \"{1}\" and \"{2}\""
msgstr "conflit dans les \"alternative_name\" \"{0}\": \"{1}\" et \"{2}\""
#: src/rougail/structural_commandline/annotator.py:96
msgid ""
"negative_description is mandatory for boolean variable, but \"{0}\" hasn't"
msgstr ""
"l'attribut negative_description est obligatoire pour des variables "
"\"boolean\", mais \"{0}\" n'en a pas"
#: src/rougail/structural_commandline/annotator.py:105
msgid ""
"negative_description is only available for boolean variable, but \"{0}\" is "
"\"{1}\""
msgstr ""
"l'attribut negative_description est seulement valide pour des variables "
"\"boolean\", mais \"{0}\" est \"{1}\""
#: src/rougail/update/update.py:741
msgid "not a XML file: {0}"
msgstr "fichier XML invalid : {0}"
#: src/rougail/utils.py:58
msgid ""
"invalid variable or family name \"{0}\" must only contains lowercase ascii "
"character, number or _"
msgstr ""
"nom invalide pour la variable ou famille \"{0}\" doit seulement contenir des "
"caractères ascii minuscule, nombre or _"
#: src/rougail/utils.py:120
msgid "error in jinja \"{0}\" for the variable \"{1}\": {2}"
msgstr "erreur dans Jinja \"{0}\" pour la variable \"{1}\": {2}"

View file

@ -1,117 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ORGANIZATION
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2024-11-04 12:04+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: pygettext.py 1.5\n"
#: src/rougail/annotator/family.py:139
msgid "default variable mode \"{0}\" is not a valid mode, valid modes are {1}"
msgstr ""
#: src/rougail/annotator/family.py:145
msgid "default family mode \"{0}\" is not a valid mode, valid modes are {1}"
msgstr ""
#: src/rougail/annotator/family.py:177
msgid "mode \"{0}\" for \"{1}\" is not a valid mode, valid modes are {2}"
msgstr ""
#: src/rougail/annotator/family.py:181
msgid "mode \"{0}\" for \"{1}\" is not a valid mode, no modes are available"
msgstr ""
#: src/rougail/annotator/family.py:245
msgid "the variable \"{0}\" is mandatory so in \"{1}\" mode but family has the higher family mode \"{2}\""
msgstr ""
#: src/rougail/annotator/family.py:283
msgid "the follower \"{0}\" is in \"{1}\" mode but leader have the higher mode \"{2}\""
msgstr ""
#: src/rougail/annotator/family.py:316
msgid "the family \"{0}\" is in \"{1}\" mode but variables and families inside have the higher modes \"{2}\""
msgstr ""
#: src/rougail/annotator/family.py:334
msgid "the variable \"{0}\" is in \"{1}\" mode but family has the higher family mode \"{2}\""
msgstr ""
#: src/rougail/annotator/value.py:77
msgid "the follower \"{0}\" without multi attribute can only have one value"
msgstr ""
#: src/rougail/annotator/value.py:93
msgid "the variable \"{0}\" is multi but has a non list default value"
msgstr ""
#: src/rougail/annotator/variable.py:189
msgid "the variable \"{0}\" has regexp attribut but has not the \"regexp\" type"
msgstr ""
#: src/rougail/annotator/variable.py:232
msgid "the variable \"{0}\" has choices attribut but has not the \"choice\" type"
msgstr ""
#: src/rougail/annotator/variable.py:260
msgid "the variable \"{0}\" has an unvalid default value \"{1}\" should be in {2}"
msgstr ""
#: src/rougail/convert.py:281
msgid "A variable or a family located in the \"{0}\" namespace shall not be used in the \"{1}\" namespace"
msgstr ""
#: src/rougail/convert.py:475
msgid "unknown type {0} for {1}"
msgstr ""
#: src/rougail/convert.py:1345
msgid "duplicate dictionary file name {0}"
msgstr ""
#: src/rougail/convert.py:1392
msgid "Cannot execute annotate multiple time"
msgstr ""
#: src/rougail/error.py:67
msgid "{0} in {1}"
msgstr ""
#: src/rougail/structural_commandline/annotator.py:67
msgid "alternative_name \"{0}\" conflict with \"--help\""
msgstr ""
#: src/rougail/structural_commandline/annotator.py:72
msgid "conflict alternative_name \"{0}\": \"{1}\" and \"{2}\""
msgstr ""
#: src/rougail/structural_commandline/annotator.py:95
msgid "negative_description is mandatory for boolean variable, but \"{0}\" hasn't"
msgstr ""
#: src/rougail/structural_commandline/annotator.py:104
msgid "negative_description is only available for boolean variable, but \"{0}\" is \"{1}\""
msgstr ""
#: src/rougail/update/update.py:738
msgid "not a XML file: {0}"
msgstr ""
#: src/rougail/utils.py:55
msgid "invalid variable or family name \"{0}\" must only contains lowercase ascii character, number or _"
msgstr ""
#: src/rougail/utils.py:117
msgid "error in jinja \"{0}\" for the variable \"{1}\": {2}"
msgstr ""

View file

@ -4,19 +4,18 @@ requires = ["flit_core >=3.8.0,<4"]
[project]
name = "rougail"
version = "1.1.1"
authors = [{name = "Emmanuel Garette", email = "gnunux@gnunux.info"}]
readme = "README.md"
version = "1.0.2"
authors = [
{name = "Emmanuel Garette", email = "gnunux@gnunux.info"},
]
description = "A consistency handling system that was initially designed in the configuration management"
readme = "README.md"
requires-python = ">=3.8"
classifiers = [
"License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)",
"Programming Language :: Python",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3",
"Operating System :: OS Independent",
"Natural Language :: English",
@ -24,10 +23,10 @@ classifiers = [
]
dependencies = [
"ruamel.yaml ~= 0.18.6",
"pydantic ~= 2.9.2",
"jinja2 ~= 3.1.4",
"tiramisu >=5.0,<6"
"ruamel.yaml ~= 0.17.40",
"pydantic ~= 2.5.2",
"jinja2 ~= 3.1.2",
"tiramisu ~= 4.1.0"
]
[project.optional-dependencies]
@ -37,14 +36,9 @@ dev = [
"lxml ~= 5.2.2"
]
[project.urls]
Home = "https://forge.cloud.silique.fr/stove/rougail"
[tool.commitizen]
name = "cz_conventional_commits"
tag_format = "$version"
version_scheme = "pep440"
version_scheme = "semver"
version_provider = "pep621"
update_changelog_on_bump = true
changelog_merge_prerelease = true

View file

@ -11,25 +11,26 @@ Copyright (C) 2019-2021
Silique (https://www.silique.fr)
Copyright (C) 2022-2024
This program is free software: you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by the
Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
distribued with GPL-2 or later license
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 Lesser General Public License for more
details.
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.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
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 tiramisu import Config, undefined
from tiramisu.error import PropertiesOptionError, LeadershipError, ConfigError
from tiramisu import Config
from tiramisu.error import PropertiesOptionError
from warnings import warn
from typing import List
from re import compile, findall
from .convert import RougailConvert
from .config import RougailConfig
@ -38,23 +39,15 @@ from .object_model import CONVERT_OPTION
from .utils import normalize_family
def tiramisu_display_name(
kls,
subconfig,
with_quote: bool = False,
) -> str:
def tiramisu_display_name(kls, subconfig) -> str:
"""Replace the Tiramisu display_name function to display path + description"""
doc = kls._get_information(subconfig, "doc", None)
comment = f" ({doc})" if doc and doc != kls.impl_getname() else ""
if "{{ identifier }}" in comment:
comment = comment.replace("{{ identifier }}", str(subconfig.identifiers[-1]))
if "{{ suffix }}" in comment:
comment = comment.replace('{{ suffix }}', str(subconfig.suffixes[-1]))
path = kls.impl_getpath()
if "{{ identifier }}" in path and subconfig.identifiers:
path = path.replace(
"{{ identifier }}", normalize_family(str(subconfig.identifiers[-1]))
)
if with_quote:
return f'"{path}"{comment}'
if "{{ suffix }}" in path:
path = path.replace('{{ suffix }}', normalize_family(str(subconfig.suffixes[-1])))
return f"{path}{comment}"
@ -84,10 +77,7 @@ class Rougail:
if not self.config:
tiram_obj = self.converted.save(self.rougailconfig["tiramisu_cache"])
optiondescription = {}
custom_types = {
custom.__name__: custom
for custom in self.rougailconfig["custom_types"].values()
}
custom_types = {custom.__name__: custom for custom in self.rougailconfig["custom_types"].values()}
exec(tiram_obj, custom_types, optiondescription) # pylint: disable=W0122
self.config = Config(
optiondescription["option_0"],
@ -97,230 +87,46 @@ class Rougail:
return self.config
def get_config(self):
warn(
"get_config is deprecated, use run instead",
DeprecationWarning,
stacklevel=2,
)
warn("get_config is deprecated, use run instead", DeprecationWarning, stacklevel=2)
return self.run()
def user_datas(self, user_datas: List[dict]):
def user_datas(self,
user_datas: List[dict]):
values = {}
errors = []
warnings = []
for datas in user_datas:
options = datas.get("options", {})
for name, data in datas.get("values", {}).items():
values[name] = {
"values": data,
"options": options.copy(),
}
errors.extend(datas.get("errors", []))
warnings.extend(datas.get("warnings", []))
self._auto_configure_dynamics(values)
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._get_variable(self.config):
path = option.path()
if path not in values:
path = path.upper()
options = values.get(path, {}).get("options", {})
if path not in values or options.get("upper") is not True:
continue
else:
options = values[path].get("options", {})
value = values[path]["values"]
if option.ismulti():
if options.get("multi_separator") and not isinstance(value, list):
value = value.split(options["multi_separator"])
values[path]["values"] = value
if options.get("needs_convert"):
value = [convert_value(option, val) for val in value]
values[path]["values"] = value
values[path]["options"]["needs_convert"] = False
elif options.get("needs_convert"):
value = convert_value(option, value)
index = option.index()
if index is not None:
if not isinstance(value, list) or index >= len(value):
continue
value = value[index]
try:
option.value.set(value)
value_is_set = True
if index is not None:
values[path]["values"][index] = undefined
if set(values[path]["values"]) == {undefined}:
values.pop(path)
else:
values.pop(path)
except Exception as err:
if path != option.path():
values[option.path()] = values.pop(path)
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():
try:
option = self.config.option(path)
value = data["values"]
if option.isfollower():
for index, val in enumerate(value):
if val is undefined:
continue
self.config.option(path, index).value.set(val)
else:
option.value.set(value)
except AttributeError as err:
errors.append(str(err))
except (ValueError, LeadershipError) as err:
# errors.append(str(err).replace('"', "'"))
errors.append(str(err))
except PropertiesOptionError as err:
# warnings.append(f'"{err}" but is defined in "{self.filename}"')
warnings.append(str(err))
return {
"errors": errors,
"warnings": warnings,
}
def _get_variable(self, config):
for subconfig in config:
if subconfig.isoptiondescription():
yield from self._get_variable(subconfig)
else:
yield subconfig
def _auto_configure_dynamics(
self,
values,
):
cache = {}
added = []
for path, data in list(values.items()):
value = data["values"]
# for value in data['values'].items():
try:
option = self.config.option(path)
option.name()
except (ConfigError, PropertiesOptionError):
pass
except AttributeError:
config = self.config
current_path = ""
identifiers = []
for name in path.split(".")[:-1]:
if current_path:
current_path += "."
current_path += name
if current_path in cache:
config, identifier = cache[current_path]
identifiers.append(identifier)
else:
tconfig = config.option(name)
try:
tconfig.group_type()
config = tconfig
except AttributeError:
for tconfig in config.list(uncalculated=True):
if tconfig.isdynamic(only_self=True):
identifier = self._get_identifier(
tconfig.name(), name
)
if identifier is None:
continue
dynamic_variable = tconfig.information.get(
"dynamic_variable",
None,
)
if not dynamic_variable:
continue
option_type = self.config.option(
dynamic_variable
).information.get("type")
if identifiers:
for s in identifiers:
dynamic_variable = dynamic_variable.replace(
"{{ identifier }}", str(s), 1
)
if dynamic_variable not in values:
values[dynamic_variable] = {"values": []}
added.append(dynamic_variable)
elif dynamic_variable not in added:
continue
config = tconfig
# option_type = option.information.get('type')
typ = CONVERT_OPTION.get(option_type, {}).get(
"func"
)
if typ:
identifier = typ(identifier)
if (
identifier
not in values[dynamic_variable]["values"]
):
values[dynamic_variable]["values"].append(
identifier
)
identifiers.append(identifier)
cache[current_path] = config, identifier
break
else:
if option.isdynamic():
parent_option = self.config.option(path.rsplit(".", 1)[0])
identifiers = self._get_identifier(
parent_option.name(uncalculated=True),
parent_option.name(),
)
dynamic_variable = None
while True:
dynamic_variable = parent_option.information.get(
"dynamic_variable",
None,
)
if dynamic_variable:
break
parent_option = self.config.option(
parent_option.path().rsplit(".", 1)[0]
)
if "." not in parent_option.path():
parent_option = None
break
if not parent_option:
continue
identifiers = parent_option.identifiers()
for identifier in identifiers:
dynamic_variable = dynamic_variable.replace(
"{{ identifier }}", str(identifier), 1
)
if dynamic_variable not in values:
values[dynamic_variable] = {"values": []}
added.append(dynamic_variable)
elif dynamic_variable not in added:
continue
option_type = option.information.get("type")
typ = CONVERT_OPTION.get(option_type, {}).get("func")
if typ:
identifier = typ(identifier)
if identifier not in values[dynamic_variable]["values"]:
values[dynamic_variable]["values"].append(identifier)
cache[option.path()] = option, identifier
def _get_identifier(self, true_name, name) -> str:
regexp = true_name.replace("{{ identifier }}", "(.*)")
finded = findall(regexp, name)
if len(finded) != 1 or not finded[0]:
return
return finded[0]
def convert_value(option, value):
if value == "":
return None
option_type = option.information.get("type")
func = CONVERT_OPTION.get(option_type, {}).get("func")
if func:
return func(value)
return value
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")

View file

@ -11,20 +11,22 @@ Copyright (C) 2019-2021
Silique (https://www.silique.fr)
Copyright (C) 2022-2024
This program is free software: you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by the
Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
distribued with GPL-2 or later license
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 Lesser General Public License for more
details.
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.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
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
"""
import importlib.resources
from os.path import isfile
from ..utils import load_modules
@ -37,23 +39,16 @@ def get_level(module):
return module.level
def get_annotators(annotators, module_name, file_name=None):
if file_name is None:
_module_name = module_name
else:
_module_name = module_name + "." + file_name
full_file_name = f"/{file_name}.py"
annotators[_module_name] = []
def get_annotators(annotators, module_name):
annotators[module_name] = []
for pathobj in importlib.resources.files(module_name).iterdir():
path = str(pathobj)
if not path.endswith(".py") or path.endswith("__.py"):
continue
if file_name is not None and not path.endswith(full_file_name):
continue
module = load_modules(module_name, path)
if "Annotator" not in dir(module):
continue
annotators[_module_name].append(module.Annotator)
annotators[module_name].append(module.Annotator)
class SpaceAnnotator: # pylint: disable=R0903
@ -73,20 +68,20 @@ class SpaceAnnotator: # pylint: disable=R0903
get_annotators(ANNOTATORS, extra_annotator)
for plugin in objectspace.plugins:
try:
get_annotators(ANNOTATORS, f"rougail.{plugin}", "annotator")
get_annotators(ANNOTATORS, f'rougail.{plugin}.annotator')
except ModuleNotFoundError:
pass
annotators = ANNOTATORS["rougail.annotator"].copy()
for extra_annotator in objectspace.extra_annotators:
annotators.extend(ANNOTATORS[extra_annotator])
for plugin in objectspace.plugins:
annotators.extend(ANNOTATORS[f"rougail.{plugin}.annotator"])
annotators.extend(ANNOTATORS[f'rougail.{plugin}.annotator'])
annotators = sorted(annotators, key=get_level)
functions = {}
functions_files = objectspace.functions_files
for functions_file in functions_files:
if isfile(functions_file):
loaded_modules = load_modules("function_file", functions_file)
loaded_modules = load_modules('function_file', functions_file)
for function in dir(loaded_modules):
if function.startswith("_"):
continue

View file

@ -11,20 +11,22 @@ Copyright (C) 2019-2021
Silique (https://www.silique.fr)
Copyright (C) 2022-2024
This program is free software: you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by the
Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
distribued with GPL-2 or later license
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 Lesser General Public License for more
details.
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.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
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 rougail.i18n import _
from rougail.error import DictConsistencyError
@ -64,19 +66,15 @@ class Annotator(Walk):
return
self.check_leadership()
self.remove_empty_families()
self.family_description()
self.family_names()
if self.objectspace.modes_level:
self.modes = {
name: Mode(idx) for idx, name in enumerate(self.objectspace.modes_level)
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()
else:
for family in self.get_families():
self.valid_mode(family)
for variable in self.get_variables():
self.valid_mode(variable)
self.convert_help()
def check_leadership(self) -> None:
@ -97,10 +95,7 @@ class Annotator(Walk):
if isinstance(family, self.objectspace.family) and not self._has_variable(
family.path
):
if (
self.objectspace.paths.default_namespace is None
or "." in family.path
):
if self.objectspace.paths.default_namespace is None or "." in family.path:
removed_families.append(family.path)
removed_families.reverse()
for family in removed_families:
@ -118,18 +113,11 @@ class Annotator(Walk):
return True
return False
def family_description(self) -> None:
def family_names(self) -> None:
"""Set doc, path, ... to family"""
for family in self.get_families():
if not family.description:
family.description = family.name
if family.type == "dynamic" and isinstance(
family.dynamic, VariableCalculation
):
path = self.objectspace.paths.get_full_path(
family.dynamic.variable,
family.path,
)
self.objectspace.informations.add(family.path, "dynamic_variable", path)
def change_modes(self):
"""change the mode of variables"""
@ -137,14 +125,16 @@ class Annotator(Walk):
default_variable_mode = self.default_variable_mode
if default_variable_mode not in modes_level:
msg = _(
'default variable mode "{0}" is not a valid mode, valid modes are {1}'
).format(default_variable_mode, modes_level)
f'default variable mode "{default_variable_mode}" is not a valid mode, '
f"valid modes are {modes_level}"
)
raise DictConsistencyError(msg, 72, None)
default_family_mode = self.default_family_mode
if default_family_mode not in modes_level:
msg = _(
'default family mode "{0}" is not a valid mode, valid modes are {1}'
).format(default_family_mode, modes_level)
f'default family mode "{default_family_mode}" is not a valid mode, '
f"valid modes are {modes_level}"
)
raise DictConsistencyError(msg, 73, None)
families = list(self.get_families())
for family in families:
@ -154,18 +144,14 @@ class Annotator(Walk):
for family in families:
self._change_family_mode(family)
if self.objectspace.paths.default_namespace is None:
for variable_path in self.objectspace.parents["."]:
for variable_path in self.objectspace.parents['.']:
variable = self.objectspace.paths[variable_path]
if (
variable.type == "symlink"
or variable_path in self.objectspace.families
):
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,
)
self._set_default_mode_variable(variable,
self.default_variable_mode,
check_level=False,
)
def valid_mode(
self,
@ -173,14 +159,10 @@ class Annotator(Walk):
) -> None:
modes_level = self.objectspace.modes_level
if self._has_mode(obj) and obj.mode not in modes_level:
if modes_level:
msg = _(
'mode "{0}" for "{1}" is not a valid mode, valid modes are {2}'
).format(obj.mode, obj.name, modes_level)
else:
msg = _(
'mode "{0}" for "{1}" is not a valid mode, no modes are available'
).format(obj.mode, obj.name)
msg = _(
f'mode "{obj.mode}" for "{obj.name}" is not a valid mode, '
f"valid modes are {modes_level}"
)
raise DictConsistencyError(msg, 71, obj.xmlfiles)
def _set_default_mode(
@ -223,7 +205,7 @@ class Annotator(Walk):
self,
variable: "self.objectspace.variable",
family_mode: Optional[str],
check_level: bool = True,
check_level: bool=True,
) -> None:
# auto_save variable is set to 'basic' mode
# if its mode is not defined by the user
@ -237,14 +219,11 @@ class Annotator(Walk):
and variable.path not in self.objectspace.default_multi
):
variable_mode = self.objectspace.modes_level[0]
if (
check_level
and 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 = _(
'the variable "{0}" is mandatory so in "{1}" mode but family has the higher family mode "{2}"'
).format(variable.name, variable_mode, family_mode)
f'the variable "{variable.name}" is mandatory so in "{variable_mode}" mode '
f'but family has the higher family mode "{family_mode}"'
)
raise DictConsistencyError(msg, 36, variable.xmlfiles)
variable.mode = variable_mode
@ -267,7 +246,9 @@ class Annotator(Walk):
if leader == follower:
# it's a leader
if not leader.mode:
self._set_auto_mode(leader, self.default_variable_mode)
self._set_auto_mode(
leader, self.default_variable_mode
)
return
if self._has_mode(follower):
follower_mode = follower.mode
@ -281,8 +262,9 @@ class Annotator(Walk):
# leader's mode is minimum level
if self._has_mode(follower):
msg = _(
'the follower "{0}" is in "{1}" mode but leader have the higher mode "{2}"'
).format(follower.name, follower_mode, leader.mode)
f'the follower "{follower.name}" is in "{follower_mode}" mode '
f'but leader have the higher mode "{leader.mode}"'
)
raise DictConsistencyError(msg, 63, follower.xmlfiles)
self._set_auto_mode(follower, leader.mode)
@ -313,9 +295,8 @@ class Annotator(Walk):
# set the lower variable mode to family
self._set_auto_mode(family, min_variable_mode)
if self.modes[family.mode] < self.modes[min_variable_mode]:
msg = _(
'the family "{0}" is in "{1}" mode but variables and families inside have the higher modes "{2}"'
).format(family.name, family.mode, 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(
@ -332,8 +313,9 @@ class Annotator(Walk):
if not is_follower and self.modes[variable_mode] < self.modes[family_mode]:
if self._has_mode(variable):
msg = _(
'the variable "{0}" is in "{1}" mode but family has the higher family mode "{2}"'
).format(variable.name, variable_mode, family_mode)
f'the variable "{variable.name}" is in "{variable_mode}" mode '
f'but family has the higher family mode "{family_mode}"'
)
raise DictConsistencyError(msg, 61, variable.xmlfiles)
self._set_auto_mode(variable, family_mode)
if not variable.mode:

View file

@ -11,20 +11,22 @@ Copyright (C) 2019-2021
Silique (https://www.silique.fr)
Copyright (C) 2022-2024
This program is free software: you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by the
Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
distribued with GPL-2 or later license
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 Lesser General Public License for more
details.
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.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
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 Union
from rougail.i18n import _
from rougail.error import DictConsistencyError
@ -121,7 +123,7 @@ class Annotator(Walk):
value = []
for calculation in frozen:
calculation_copy = calculation.copy()
calculation_copy.attribute_name = "frozen"
calculation_copy.attribute_name = 'frozen'
calculation_copy.ori_path = calculation_copy.path
calculation_copy.path = path
value.append(calculation_copy)

View file

@ -11,20 +11,22 @@ Copyright (C) 2019-2021
Silique (https://www.silique.fr)
Copyright (C) 2022-2024
This program is free software: you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by the
Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
distribued with GPL-2 or later license
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 Lesser General Public License for more
details.
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.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
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.i18n import _
@ -53,7 +55,7 @@ class Annotator(Walk): # pylint: disable=R0903
for variable in self.get_variables():
if variable.type == "symlink":
continue
if variable.version != "1.0" and variable.type == "port":
if variable.version != '1.0' and variable.type == 'port':
self._convert_port(variable)
self._convert_value(variable)
@ -75,24 +77,24 @@ class Annotator(Walk): # pylint: disable=R0903
raise DictConsistencyError(msg, 68, variable.xmlfiles)
if variable.path in self.objectspace.followers and multi != "submulti":
msg = _(
'the follower "{0}" without multi attribute can only have one value'
).format(variable.name)
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
self.objectspace.default_multi[
variable.path
] = variable.default
variable.default = None
else:
self.objectspace.default_multi[variable.path] = (
variable.default[0]
)
self.objectspace.default_multi[variable.path] = variable.default[
0
]
elif variable.multi:
msg = _(
'the variable "{0}" is multi but has a non list default value'
).format(variable.name)
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

View file

@ -11,18 +11,21 @@ Copyright (C) 2019-2021
Silique (https://www.silique.fr)
Copyright (C) 2022-2024
This program is free software: you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by the
Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
distribued with GPL-2 or later license
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 Lesser General Public License for more
details.
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.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
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.i18n import _
@ -63,23 +66,20 @@ class Annotator(Walk): # pylint: disable=R0903
return
self.objectspace = objectspace
if self.objectspace.main_namespace:
self.forbidden_name = [self.objectspace.main_namespace]
self.forbidden_name = [
self.objectspace.main_namespace
]
for extra in self.objectspace.extra_dictionaries:
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.verify_choices()
self.basic_types = {str: "string", int: "number", bool: "boolean", float: "float"}
self.convert_variable()
self.convert_test()
self.convert_examples()
self.convert_help()
self.verify_choices()
def convert_variable(self):
"""convert variable"""
@ -97,12 +97,14 @@ class Annotator(Walk): # pylint: disable=R0903
variable.multi = False
if variable.type is None:
variable.type = "string"
self.objectspace.informations.add(variable.path, "type", variable.type)
self.objectspace.informations.add(
variable.path, "type", variable.type
)
self._convert_variable(variable)
def _convert_variable_inference(
self,
variable,
self,
variable,
) -> None:
# variable has no type
if variable.type is None:
@ -118,32 +120,23 @@ class Annotator(Walk): # pylint: disable=R0903
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.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,
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
):
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, identifier = self.objectspace.paths.get_with_dynamic(
calculated_variable_path,
variable.default.path_prefix,
variable.path,
variable.version,
variable.namespace,
variable.xmlfiles,
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
@ -153,11 +146,7 @@ class Annotator(Walk): # pylint: disable=R0903
# 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]
):
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
@ -182,16 +171,15 @@ class Annotator(Walk): # pylint: disable=R0903
family = self.objectspace.paths[variable.path.rsplit(".", 1)[0]]
if variable.hidden:
family.hidden = variable.hidden
# elif family.hidden:
# variable.hidden = family.hidden
elif family.hidden:
variable.hidden = family.hidden
variable.hidden = None
if variable.regexp is not None and variable.type != "regexp":
msg = _(
'the variable "{0}" has regexp attribut but has not the "regexp" type'
).format(variable.path)
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)
if variable.mandatory is None:
variable.mandatory = True
def convert_test(self):
"""Convert variable tests value"""
@ -225,20 +213,8 @@ class Annotator(Walk): # pylint: disable=R0903
def verify_choices(self):
for variable in self.get_variables():
if variable.type is None and variable.choices:
# choice type inference from the `choices` attribute
variable.type = "choice"
if variable.choices is not None and variable.type != "choice":
msg = _(
'the variable "{0}" has choices attribut but has not the "choice" type'
).format(variable.path)
raise DictConsistencyError(msg, 11, variable.xmlfiles)
if variable.type != "choice":
if variable.type != 'choice' or variable.default is None:
continue
if variable.default is None:
continue
if None in variable.choices and variable.mandatory is None:
variable.mandatory = False
if not isinstance(variable.choices, list):
continue
choices = variable.choices
@ -257,11 +233,5 @@ class Annotator(Walk): # pylint: disable=R0903
if isinstance(value, Calculation):
continue
if value not in choices:
msg = _(
'the variable "{0}" has an unvalid default value "{1}" should be in {2}'
).format(
variable.path,
value,
display_list(choices, separator="or", add_quote=True),
)
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)

View file

@ -12,20 +12,22 @@ Copyright (C) 2019-2021
Silique (https://www.silique.fr)
Copyright (C) 2022-2024
This program is free software: you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by the
Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
distribued with GPL-2 or later license
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 Lesser General Public License for more
details.
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.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
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 pathlib import Path
from tiramisu import Config
from ruamel.yaml import YAML
@ -33,14 +35,12 @@ from .utils import _, load_modules, normalize_family
from .convert import RougailConvert
RENAMED = {
"dictionaries_dir": "main_dictionaries",
"variable_namespace": "main_namespace",
"functions_file": "functions_files",
}
NOT_IN_TIRAMISU = {
"custom_types": {},
}
RENAMED = {'dictionaries_dir': 'main_dictionaries',
'variable_namespace': 'main_namespace',
'functions_file': 'functions_files',
}
NOT_IN_TIRAMISU = {'custom_types': {},
}
SUBMODULES = None
@ -49,27 +49,29 @@ def get_sub_modules():
if SUBMODULES is None:
SUBMODULES = {}
for submodule in Path(__file__).parent.iterdir():
if submodule.name.startswith("_") or not submodule.is_dir():
if submodule.name.startswith('_') or not submodule.is_dir():
continue
config_file = submodule / "config.py"
config_file = submodule / 'config.py'
if config_file.is_file():
SUBMODULES[submodule.name] = load_modules(
"rougail." + submodule.name + ".config", str(config_file)
)
SUBMODULES[submodule.name] = load_modules('rougail.' + submodule.name + '.config', str(config_file))
return SUBMODULES
def get_level(module):
return module["level"]
return module['level']
class _RougailConfig:
def __init__(self, backward_compatibility: bool, root, extra_vars: dict):
def __init__(self,
backward_compatibility: bool,
root,
extra_vars: dict
):
self.backward_compatibility = backward_compatibility
self.root = root
self.config = Config(
self.root,
)
self.root,
)
self.config.property.read_only()
self.extra_vars = extra_vars
self.not_in_tiramisu = NOT_IN_TIRAMISU | extra_vars
@ -79,9 +81,7 @@ class _RougailConfig:
setattr(self, variable, default_value)
def copy(self):
rougailconfig = _RougailConfig(
self.backward_compatibility, self.root, self.extra_vars
)
rougailconfig = _RougailConfig(self.backward_compatibility, self.root, self.extra_vars)
rougailconfig.config.value.importation(self.config.value.exportation())
rougailconfig.config.property.importation(self.config.property.exportation())
rougailconfig.config.property.read_only()
@ -92,17 +92,16 @@ class _RougailConfig:
setattr(rougailconfig, variable, value)
return rougailconfig
def __setitem__(
self,
key,
value,
) -> None:
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"
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():
@ -112,29 +111,30 @@ class _RougailConfig:
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":
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:
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"
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":
if key == 'not_export_with_import':
return not ret
return ret
def get_leadership(self, option) -> dict:
def get_leadership(self,
option
) -> dict:
leader = None
followers = []
for opt, value in option.value.get().items():
@ -151,7 +151,7 @@ class _RougailConfig:
if option.isoptiondescription():
yield from self.parse(option)
elif not option.issymlinkoption():
yield f"{option.path()}: {option.value.get()}"
yield f'{option.path()}: {option.value.get()}'
def __repr__(self):
self.config.property.read_write()
@ -164,17 +164,16 @@ class _RougailConfig:
class FakeRougailConvert(RougailConvert):
def __init__(
self,
add_extra_options: bool,
) -> None:
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.suffix = ''
self.custom_types = {}
self.functions_files = []
self.modes_level = []
@ -182,20 +181,19 @@ class FakeRougailConvert(RougailConvert):
self.base_option_name = "baseoption"
self.export_with_import = True
self.internal_functions = []
self.plugins = ["structural_commandline"]
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:
def get_rougail_config(*,
backward_compatibility: bool=True,
add_extra_options: bool=True,
) -> _RougailConfig:
if backward_compatibility:
main_namespace_default = "rougail"
main_namespace_default = 'rougail'
else:
main_namespace_default = "null"
rougail_options = f"""default_dictionary_format_version:
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:
@ -221,7 +219,7 @@ sort_dictionaries_all:
main_namespace:
description: Main namespace name
default: {main_namespace_default}
default: MAIN_MAMESPACE_DEFAULT
alternative_name: s
mandatory: false
@ -291,29 +289,18 @@ functions_files:
modes_level:
description: All modes level available
multi: true
mandatory: false
"""
if backward_compatibility:
rougail_options += """
default:
- basic
- standard
- advanced
"""
rougail_options += """
commandline: false
default_family_mode:
description: Default mode for a family
default:
type: jinja
jinja: |
{% if modes_level %}
{{ modes_level[0] }}
{% endif %}
disabled:
jinja: |
{% if not modes_level %}
No mode
{% endif %}
validators:
- type: jinja
jinja: |
@ -325,19 +312,9 @@ default_family_mode:
default_variable_mode:
description: Default mode for a variable
default:
type: jinja
jinja: |
{% if modes_level %}
{% if modes_level | length == 1 %}
{{ modes_level[0] }}
{% else %}
{{ modes_level[1] }}
{% endif %}
{% endif %}
disabled:
jinja: |
{% if not modes_level %}
No mode
{% endif %}
validators:
- type: jinja
jinja: |
@ -387,15 +364,14 @@ suffix:
default: ''
mandatory: false
commandline: false
"""
processes = {
"structural": [],
"output": [],
"user data": [],
}
""".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)
processes[data['process']].append(data)
# reorder
for process in processes:
processes[process] = list(sorted(processes[process], key=get_level))
@ -410,22 +386,17 @@ suffix:
description: Select for {NAME}
alternative_name: {NAME[0]}
choices:
""".format(
NAME=normalize_family(process),
)
""".format(NAME=normalize_family(process),
)
for obj in objects:
rougail_process += f" - {obj['name']}\n"
if process == "structural":
if process == 'structural':
rougail_process += " commandline: false"
elif process == "user data":
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)
]
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
@ -435,63 +406,49 @@ suffix:
rougail_process += """ {% if _.output == 'NAME' %}
Cannot load user data for NAME output
{% endif %}
""".replace(
"NAME", hidden_output
)
elif objects:
rougail_process += " default: {DEFAULT}".format(
DEFAULT=objects[0]["name"]
)
else:
if process == 'output':
prop = 'hidden'
""".replace('NAME', hidden_output)
else:
prop = 'disabled'
rougail_process += ' default: {DEFAULT}'.format(DEFAULT=objects[0]['name'])
else:
rougail_process += """
{NAME}:
description: Select for {NAME}
hidden: true
mandatory: false
{PROP}: true
multi: true
default: ["You haven't installed \\\"{NAME}\\\" package for rougail"]
validators:
- jinja: Please install a rougail-{NAME}-* package.
""".format(
NAME=normalize_family(process),
PROP=prop,
)
""".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",
'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:
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"]),
'',
'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,
)
return _RougailConfig(backward_compatibility,
optiondescription["option_0"],
extra_vars=extra_vars,
)
RougailConfig = get_rougail_config()

View file

@ -12,20 +12,22 @@ Copyright (C) 2019-2021
Silique (https://www.silique.fr)
Copyright (C) 2022-2024
This program is free software: you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by the
Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
distribued with GPL-2 or later license
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 Lesser General Public License for more
details.
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.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
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
"""
import logging
from itertools import chain
from pathlib import Path
@ -52,6 +54,7 @@ from warnings import warn
from tiramisu.error import display_list
from .annotator import SpaceAnnotator
from .error import DictConsistencyError
from .i18n import _
from .object_model import CONVERT_OPTION # Choice,
from .object_model import (
@ -130,11 +133,10 @@ class Paths:
if not force and is_dynamic:
self._dynamics[path] = dynamic
def get_full_path(
self,
path: str,
current_path: str,
):
def get_relative_path(self,
path: str,
current_path: str,
):
relative, subpath = path.split(".", 1)
relative_len = len(relative)
path_len = current_path.count(".")
@ -146,82 +148,61 @@ class Paths:
def get_with_dynamic(
self,
path: str,
identifier_path: str,
suffix_path: str,
current_path: str,
version: str,
namespace: str,
xmlfiles: List[str],
) -> Any:
identifier = None
if version != "1.0" and self.regexp_relative.search(path):
path = self.get_full_path(
path,
current_path,
)
suffix = None
if version != '1.0' and self.regexp_relative.search(path):
path = self.get_relative_path(path,
current_path,
)
else:
path = get_realpath(path, identifier_path)
path = get_realpath(path, suffix_path)
dynamic = None
# version 1.0
if version == "1.0":
if not path in self._data and "{{ suffix }}" not in path:
new_path = None
current_path = None
identifiers = []
for name in path.split("."):
parent_path = current_path
if current_path:
current_path += "." + name
else:
current_path = name
if current_path in self._data:
if new_path:
new_path += "." + name
else:
new_path = name
continue
for dynamic_path in self._dynamics:
if "." in dynamic_path:
parent_dynamic, name_dynamic = dynamic_path.rsplit(".", 1)
else:
parent_dynamic = None
name_dynamic = dynamic_path
if (
parent_dynamic == parent_path
and name_dynamic.endswith("{{ identifier }}")
and name == name_dynamic.replace("{{ identifier }}", "")
):
new_path += "." + name_dynamic
break
regexp = "^" + name_dynamic.replace("{{ identifier }}", "(.*)")
finded = findall(regexp, name)
if len(finded) != 1 or not finded[0]:
continue
if finded[0] == "{{ identifier }}":
identifiers.append(None)
else:
identifiers.append(finded[0])
if new_path is None:
new_path = name_dynamic
else:
new_path += "." + name_dynamic
parent_path = dynamic_path
break
else:
if new_path:
new_path += "." + name
else:
new_path = name
path = new_path
else:
identifiers = None
if "{{ suffix }}" in path:
path = path.replace("{{ suffix }}", "{{ identifier }}")
elif not path in self._data:
if not path in self._data and "{{ suffix }}" not in path:
new_path = None
current_path = None
parent_path = None
new_path = current_path
identifiers = []
for name in path.split("."):
parent_path = current_path
if current_path:
current_path += "." + name
else:
current_path = name
if current_path in self._data:
if new_path:
new_path += "." + name
else:
new_path = name
continue
for dynamic_path in self._dynamics:
if '.' in dynamic_path:
parent_dynamic, name_dynamic = dynamic_path.rsplit(".", 1)
else:
parent_dynamic = None
name_dynamic = dynamic_path
if (
version == "1.0"
and parent_dynamic == parent_path
and name_dynamic.endswith("{{ suffix }}")
and name == name_dynamic.replace("{{ suffix }}", "")
):
new_path += "." + name_dynamic
break
else:
if new_path:
new_path += "." + name
else:
new_path = name
path = new_path
if not path in self._data:
current_path = None
new_path = current_path
suffixes = []
for name in path.split("."):
parent_path = current_path
if current_path:
current_path += "." + name
else:
@ -232,44 +213,38 @@ class Paths:
new_path += "." + name
else:
new_path = name
parent_path = current_path
continue
for dynamic_path in self._dynamics:
if "." in dynamic_path:
if '.' in dynamic_path:
parent_dynamic, name_dynamic = dynamic_path.rsplit(".", 1)
else:
parent_dynamic = None
name_dynamic = dynamic_path
if (
"{{ identifier }}" not in name_dynamic
"{{ suffix }}" not in name_dynamic
or parent_path != parent_dynamic
):
continue
regexp = "^" + name_dynamic.replace("{{ identifier }}", "(.*)")
regexp = "^" + name_dynamic.replace("{{ suffix }}", "(.*)")
finded = findall(regexp, name)
if len(finded) != 1 or not finded[0]:
continue
if finded[0] == "{{ identifier }}":
identifiers.append(None)
else:
identifiers.append(finded[0])
suffixes.append(finded[0])
if new_path is None:
new_path = name_dynamic
else:
new_path += "." + name_dynamic
parent_path = dynamic_path
break
else:
if new_path:
new_path += "." + name
else:
new_path = name
if "{{ identifier }}" in name:
identifiers.append(None)
parent_path = current_path
if "{{ suffix }}" in name:
suffixes.append(None)
path = new_path
else:
identifiers = None
suffixes = None
if path not in self._data:
return None, None
option = self._data[path]
@ -279,10 +254,11 @@ class Paths:
and namespace != option_namespace
):
msg = _(
'A variable or a family located in the "{0}" namespace shall not be used in the "{1}" namespace'
).format(option_namespace, namespace)
f'A variable or a family located in the "{option_namespace}" namespace '
f'shall not be used in the "{namespace}" namespace'
)
raise DictConsistencyError(msg, 38, xmlfiles)
return option, identifiers
return option, suffixes
def __getitem__(
self,
@ -340,8 +316,8 @@ class Informations:
class ParserVariable:
def __init__(self, rougailconfig):
self.load_config(rougailconfig)
self.rougailconfig = rougailconfig
self.load_config()
self.paths = Paths(self.main_namespace)
self.families = []
self.variables = []
@ -366,34 +342,27 @@ class ParserVariable:
self.is_init = False
super().__init__()
def load_config(self) -> None:
rougailconfig = self.rougailconfig
self.sort_dictionaries_all = rougailconfig["sort_dictionaries_all"]
try:
self.main_dictionaries = rougailconfig["main_dictionaries"]
except:
self.main_dictionaries = []
def load_config(self,
rougailconfig: 'RougailConfig',
) -> None:
self.rougailconfig = rougailconfig
self.main_namespace = rougailconfig["main_namespace"]
self.main_dictionaries = rougailconfig["main_dictionaries"]
if self.main_namespace:
self.extra_dictionaries = rougailconfig["extra_dictionaries"]
self.suffix = rougailconfig["suffix"]
self.default_dictionary_format_version = rougailconfig[
"default_dictionary_format_version"
]
self.default_dictionary_format_version = rougailconfig["default_dictionary_format_version"]
self.custom_types = rougailconfig["custom_types"]
self.functions_files = rougailconfig["functions_files"]
self.modes_level = rougailconfig["modes_level"]
if self.modes_level:
self.default_variable_mode = rougailconfig["default_variable_mode"]
self.default_family_mode = rougailconfig["default_family_mode"]
self.default_variable_mode = rougailconfig["default_variable_mode"]
self.default_family_mode = rougailconfig["default_family_mode"]
self.extra_annotators = rougailconfig["extra_annotators"]
self.base_option_name = rougailconfig["base_option_name"]
self.export_with_import = rougailconfig["export_with_import"]
self.internal_functions = rougailconfig["internal_functions"]
self.add_extra_options = rougailconfig[
"structural_commandline.add_extra_options"
]
self.plugins = rougailconfig["plugins"]
self.add_extra_options = rougailconfig["structural_commandline.add_extra_options"]
self.plugins = []
def _init(self):
if self.is_init:
@ -403,22 +372,14 @@ class ParserVariable:
if self.plugins:
root = Path(__file__).parent
for plugin in self.plugins:
module_path = root / plugin / "object_model.py"
module_path = root / plugin / 'object_model.py'
if not module_path.is_file():
continue
module = load_modules(
f"rougail.{plugin}.object_model", str(module_path)
)
if "Variable" in module.__all__:
variable = type(
variable.__name__ + "_" + plugin,
(variable, module.Variable),
{},
)
if "Family" in module.__all__:
family = type(
family.__name__ + "_" + plugin, (family, module.Family), {}
)
module = load_modules(f'rougail.{plugin}.object_model', str(module_path))
if 'Variable' in module.__all__:
variable = type(variable.__name__ + '_' + plugin, (variable, module.Variable), {})
if 'Family' in module.__all__:
family = type(family.__name__ + '_' + plugin, (family, module.Family), {})
self.variable = variable
self.family = family
self.dynamic = type(Dynamic.__name__, (Dynamic, family), {})
@ -472,7 +433,7 @@ class ParserVariable:
return "family"
if obj_type in self.variable_types:
return "variable"
msg = _("unknown type {0} for {1}").format(obj_type, path)
msg = f"unknown type {obj_type} for {path}"
raise DictConsistencyError(msg, 43, [filename])
# in a leadership there is only variable
if family_is_leadership:
@ -483,15 +444,11 @@ class ParserVariable:
extra_keys = set(obj) - self.variable_attrs
if not extra_keys:
for key, value in obj.items():
if (
isinstance(value, dict)
and key != "params"
and not self.is_calculation(
key,
value,
self.variable_calculations,
False,
)
if isinstance(value, dict) and not self.is_calculation(
key,
value,
self.variable_calculations,
False,
):
break
else:
@ -600,10 +557,8 @@ class ParserVariable:
# it's just for modify subfamily or subvariable, do not redefine
if family_obj:
if not obj.pop("redefine", False):
raise DictConsistencyError(
f'The family "{path}" already exists and it is not redefined',
32,
[filename],
raise Exception(
f"The family {path} already exists and she is not redefined in {filename}"
)
# convert to Calculation objects
self.parse_parameters(
@ -638,29 +593,26 @@ class ParserVariable:
obj_type = self.get_family_or_variable_type(family_obj)
if obj_type is None:
# auto set type
if "_dynamic" in family_obj:
dynamic = family_obj["_dynamic"]
elif "dynamic" in family_obj:
dynamic = family_obj["dynamic"]
if '_dynamic' in family_obj:
dynamic = family_obj['_dynamic']
elif 'dynamic' in family_obj:
dynamic = family_obj['dynamic']
else:
dynamic = None
if isinstance(dynamic, (list, dict)):
family_obj["type"] = obj_type = "dynamic"
family_obj['type'] = obj_type = 'dynamic'
if obj_type == "dynamic":
family_is_dynamic = True
if "{{ identifier }}" not in name:
if version == "1.0" and "{{ suffix }}" in name:
name = name.replace("{{ suffix }}", "{{ identifier }}")
path = path.replace("{{ suffix }}", "{{ identifier }}")
elif "variable" in family_obj:
name += "{{ identifier }}"
path += "{{ identifier }}"
else:
msg = f'dynamic family name must have "{{{{ identifier }}}}" in his name for "{path}"'
raise DictConsistencyError(msg, 13, [filename])
parent_dynamic = path
if version != "1.0" and not family_obj and comment:
family_obj["description"] = comment
if '{{ suffix }}' not in name:
if "variable" in family_obj:
name += '{{ suffix }}'
path += '{{ suffix }}'
else:
msg = f'dynamic family name must have "{{{{ suffix }}}}" in his name for "{path}"'
raise DictConsistencyError(msg, 13, [filename])
if version != '1.0' and not family_obj and comment:
family_obj['description'] = comment
self.add_family(
path,
name,
@ -716,11 +668,7 @@ class ParserVariable:
# it's a dict, so a new variables!
continue
# 'variable' for compatibility to format 1.0
if (
key == "variable"
and obj.get("type") != "dynamic"
and obj.get("_type") != "dynamic"
):
if key == "variable" and obj.get("type") != "dynamic" and obj.get("_type") != "dynamic":
continue
if key in self.family_attrs:
yield key
@ -765,20 +713,17 @@ class ParserVariable:
del family["variable"]
# FIXME only for 1.0
if "variable" in family:
family["dynamic"] = {
"type": "variable",
"variable": family["variable"],
"propertyerror": False,
"allow_none": True,
}
del family["variable"]
family['dynamic'] = {'type': 'variable',
'variable': family['variable'],
'propertyerror': False,
'allow_none': True,
}
del family['variable']
if version != "1.0":
warning = f'"variable" attribute in dynamic family "{ path }" is depreciated in {filename}'
warn(warning)
if "variable" in family:
raise Exception(
f'dynamic family must not have "variable" attribute for "{family["path"]}" in {family["xmlfiles"]}'
)
raise Exception(f'dynamic family must not have "variable" attribute for "{family["path"]}" in {family["xmlfiles"]}')
else:
family_obj = self.family
# convert to Calculation objects
@ -993,7 +938,6 @@ class ParserVariable:
variable["namespace"] = self.namespace
variable["version"] = version
variable["path_prefix"] = self.path_prefix
variable["xmlfiles"] = filename
variable_type = self.get_family_or_variable_type(variable)
obj = {
@ -1013,7 +957,7 @@ class ParserVariable:
parent_dynamic,
)
self.variables.append(variable["path"])
if "." in variable["path"]:
if '.' in variable["path"]:
parent_path = variable["path"].rsplit(".", 1)[0]
else:
parent_path = "."
@ -1031,10 +975,10 @@ class ParserVariable:
del self.paths[path]
self.families.remove(path)
del self.parents[path]
if "." in path:
if '.' in path:
parent = path.rsplit(".", 1)[0]
else:
parent = "."
parent = '.'
self.parents[parent].remove(path)
###############################################################################################
@ -1047,7 +991,9 @@ class ParserVariable:
):
"""Set Tiramisu object name"""
self.index += 1
self.reflector_names[obj.path] = f"{option_prefix}{self.index}{self.suffix}"
self.reflector_names[
obj.path
] = f'{option_prefix}{self.index}{self.suffix}'
###############################################################################################
# calculations
@ -1066,12 +1012,12 @@ class ParserVariable:
calculations = calculations[1]
if not isinstance(value, dict) or attribute not in calculations:
return False
if "type" in value:
return value["type"] in CALCULATION_TYPES
if 'type' in value:
return value['type'] in CALCULATION_TYPES
# auto set type
typ = set(CALCULATION_TYPES) & set(value)
if len(typ) == 1:
value["type"] = list(typ)[0]
value['type'] = list(typ)[0]
return True
return False
@ -1110,7 +1056,7 @@ class ParserVariable:
# auto set type
param_typ = set(CALCULATION_TYPES) & set(val)
if len(param_typ) == 1:
val["type"] = list(param_typ)[0]
val['type'] = list(param_typ)[0]
if not isinstance(val, dict) or "type" not in val:
param_typ = "any"
val = {
@ -1118,8 +1064,6 @@ class ParserVariable:
"type": "any",
}
else:
if version == "1.0" and val["type"] == "suffix":
val["type"] = "identifier"
param_typ = val["type"]
val["key"] = key
val["path"] = path
@ -1130,10 +1074,8 @@ class ParserVariable:
try:
params.append(PARAM_TYPES[param_typ](**val))
except ValidationError as err:
raise DictConsistencyError(
f'"{attribute}" has an invalid "{key}" for {path}: {err}',
29,
xmlfiles,
raise Exception(
f'"{attribute}" has an invalid "{key}" for {path}: {err}'
) from err
calculation_object["params"] = params
#
@ -1144,8 +1086,8 @@ class ParserVariable:
f'unknown "return_type" in {attribute} of variable "{path}"'
)
#
if typ == "identifier" and not family_is_dynamic:
msg = f'identifier calculation for "{attribute}" in "{path}" cannot be set none dynamic family'
if typ == "suffix" and not family_is_dynamic:
msg = f'suffix calculation for "{attribute}" in "{path}" cannot be set none dynamic family'
raise DictConsistencyError(msg, 53, xmlfiles)
if attribute in PROPERTY_ATTRIBUTE:
calc = CALCULATION_PROPERTY_TYPES[typ](**calculation_object)
@ -1201,20 +1143,19 @@ class RougailConvert(ParserVariable):
"""Parse directories content"""
self._init()
if path_prefix:
n_path_prefix = normalize_family(path_prefix)
if n_path_prefix in self.parents:
if path_prefix in self.parents:
raise Exception("pfffff")
root_parent = n_path_prefix
self.path_prefix = n_path_prefix
root_parent = path_prefix
self.path_prefix = path_prefix
self.namespace = None
self.add_family(
n_path_prefix,
n_path_prefix,
{"description": path_prefix},
path_prefix,
path_prefix,
{},
"",
False,
None,
"",
'',
)
else:
root_parent = "."
@ -1242,13 +1183,12 @@ class RougailConvert(ParserVariable):
for idx, filename in enumerate(self.get_sorted_filename(extra_dirs)):
if not idx:
self.parse_family(
"",
'',
self.namespace,
namespace_path,
{
"description": namespace,
},
"",
{'description': namespace,
},
'',
)
self.parse_variable_file(
filename,
@ -1257,7 +1197,7 @@ class RougailConvert(ParserVariable):
else:
self.namespace = None
if root_parent == ".":
namespace_path = ""
namespace_path = ''
else:
namespace_path = f"{root_parent}"
if namespace_path in self.parents:
@ -1297,20 +1237,18 @@ class RougailConvert(ParserVariable):
)
if objects is None:
return
self.parse_root_file(
filename,
path,
version,
objects,
)
self.parse_root_file(filename,
path,
version,
objects,
)
def parse_root_file(
self,
filename: str,
path: str,
version: str,
objects: dict,
) -> None:
def parse_root_file(self,
filename: str,
path: str,
version: str,
objects: dict,
) -> None:
for name, obj in objects.items():
comment = self.get_comment(name, objects)
self.family_or_variable(
@ -1329,28 +1267,21 @@ class RougailConvert(ParserVariable):
"""Sort filename"""
if not isinstance(directories, list):
directories = [directories]
if self.sort_dictionaries_all:
filenames = {}
for directory_name in directories:
directory = Path(directory_name)
if not directory.is_dir():
continue
if not self.sort_dictionaries_all:
filenames = {}
filenames = {}
for file_path in directory.iterdir():
if file_path.suffix not in [".yml", ".yaml"]:
continue
if file_path.name in filenames:
raise DictConsistencyError(
_("duplicate dictionary file name {0}").format(file_path.name),
_(f"duplicate dictionary file name {file_path.name}"),
78,
[filenames[file_path.name][1]],
)
filenames[file_path.name] = str(file_path)
if not self.sort_dictionaries_all:
for filename in sorted(filenames):
yield filenames[filename]
if self.sort_dictionaries_all:
for filename in sorted(filenames):
yield filenames[filename]
@ -1416,5 +1347,5 @@ class RougailConvert(ParserVariable):
if filename:
with open(filename, "w", encoding="utf-8") as tiramisu:
tiramisu.write(output)
# print(output)
#print(output)
return output

View file

@ -11,20 +11,22 @@ Copyright (C) 2019-2021
Silique (https://www.silique.fr)
Copyright (C) 2022-2024
This program is free software: you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by the
Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
distribued with GPL-2 or later license
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 Lesser General Public License for more
details.
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.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
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 .i18n import _
@ -64,7 +66,7 @@ class DictConsistencyError(Exception):
def __init__(self, msg, errno, xmlfiles):
if xmlfiles:
msg = _("{0} in {1}").format(msg, display_xmlfiles(xmlfiles))
msg = _(f"{msg} in {display_xmlfiles(xmlfiles)}")
super().__init__(msg)
self.errno = errno
@ -72,17 +74,13 @@ class DictConsistencyError(Exception):
class UpgradeError(Exception):
"""Error during XML upgrade"""
## ---- generic exceptions ----
class NotFoundError(Exception):
"not found error"
pass
## ---- specific exceptions ----
class VariableCalculationDependencyError(Exception):
pass

View file

@ -10,23 +10,51 @@ Copyright (C) 2019-2021
Silique (https://www.silique.fr)
Copyright (C) 2022-2024
This program is free software: you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by the
Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
distribued with GPL-2 or later license
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 Lesser General Public License for more
details.
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.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
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
"""
import gettext
import os
import sys
import locale
from gettext import translation
from pathlib import Path
# Application Name
APP_NAME = "rougail"
t = translation("rougail", str(Path(__file__).parent / "locale"), fallback=True)
# Traduction dir
APP_DIR = os.path.join(sys.prefix, "share")
LOCALE_DIR = os.path.join(APP_DIR, "locale")
# Default Lanugage
DEFAULT_LANG = os.environ.get("LANG", "").split(":")
DEFAULT_LANG += ["en_US"]
languages = []
lc, encoding = locale.getlocale()
if lc:
languages = [lc]
languages += DEFAULT_LANG
mo_location = LOCALE_DIR
gettext.find(APP_NAME, mo_location)
gettext.textdomain(APP_NAME)
# gettext.bind_textdomain_codeset(APP_NAME, "UTF-8")
# gettext.translation(APP_NAME, fallback=True)
t = gettext.translation(APP_NAME, fallback=True)
_ = t.gettext

View file

@ -3,18 +3,21 @@
Silique (https://www.silique.fr)
Copyright (C) 2023-2024
This program is free software: you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by the
Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
distribued with GPL-2 or later license
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 Lesser General Public License for more
details.
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.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
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, Union, get_type_hints, Any, Literal, List, Dict, Iterator
@ -134,16 +137,16 @@ class VariableParam(Param):
optional: bool = False
class IdentifierParam(Param):
class SuffixParam(Param):
type: str
identifier: Optional[int] = None
suffix: Optional[int] = None
def __init__(
self,
**kwargs,
) -> None:
if not kwargs["family_is_dynamic"]:
msg = f'identifier parameter for "{kwargs["attribute"]}" in "{kwargs["path"]}" cannot be set none dynamic family'
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)
@ -171,7 +174,7 @@ class IndexParam(Param):
PARAM_TYPES = {
"any": AnyParam,
"variable": VariableParam,
"identifier": IdentifierParam,
"suffix": SuffixParam,
"information": InformationParam,
"index": IndexParam,
}
@ -206,7 +209,7 @@ class Calculation(BaseModel):
path = self.path
else:
path = self.ori_path
variable, identifier = objectspace.paths.get_with_dynamic(
variable, suffix = objectspace.paths.get_with_dynamic(
param["variable"],
self.path_prefix,
path,
@ -222,15 +225,15 @@ class Calculation(BaseModel):
if not isinstance(variable, objectspace.variable):
raise Exception("pfff it's a family")
param["variable"] = variable
if identifier:
param["identifier"] = identifier
if suffix:
param["suffix"] = suffix
if param.get("type") == "information":
if param["variable"]:
if self.ori_path is None:
path = self.path
else:
path = self.ori_path
variable, identifier = objectspace.paths.get_with_dynamic(
variable, suffix = objectspace.paths.get_with_dynamic(
param["variable"],
self.path_prefix,
path,
@ -242,7 +245,7 @@ class Calculation(BaseModel):
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 identifier:
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:
@ -308,7 +311,7 @@ class JinjaCalculation(Calculation):
path = self.path
else:
path = self.ori_path
for sub_variable, identifier, true_path in get_jinja_variable_to_param(
for sub_variable, suffix, true_path in get_jinja_variable_to_param(
path,
self.jinja,
objectspace,
@ -330,10 +333,8 @@ class JinjaCalculation(Calculation):
"type": "variable",
"variable": sub_variable,
}
if self.version != "1.0":
default["params"][true_path]["propertyerror"] = False
if identifier:
default["params"][true_path]["identifier"] = identifier
if suffix:
default["params"][true_path]["suffix"] = suffix
return default
def to_function(
@ -402,15 +403,14 @@ class _VariableCalculation(Calculation):
propertyerror: bool = True
allow_none: bool = False
def get_variable(
self,
objectspace,
) -> "Variable":
def get_variable(self,
objectspace,
) -> "Variable":
if self.ori_path is None:
path = self.path
else:
path = self.ori_path
variable, identifier = objectspace.paths.get_with_dynamic(
variable, suffix = objectspace.paths.get_with_dynamic(
self.variable,
self.path_prefix,
path,
@ -421,13 +421,13 @@ class _VariableCalculation(Calculation):
if variable and not isinstance(variable, objectspace.variable):
# FIXME remove the pfff
raise Exception("pfff it's a family")
return variable, identifier
return variable, suffix
def get_params(
self,
objectspace,
variable: "Variable",
identifier: Optional[str],
suffix: Optional[str],
*,
needs_multi: Optional[bool] = None,
):
@ -439,8 +439,8 @@ class _VariableCalculation(Calculation):
"variable": variable,
"propertyerror": self.propertyerror,
}
if identifier:
param["identifier"] = identifier
if suffix:
param["suffix"] = suffix
params = {None: [param]}
if self.default_values:
params["__default_value"] = self.default_values
@ -454,7 +454,7 @@ class _VariableCalculation(Calculation):
calc_variable_is_multi = variable.path in objectspace.multis
if not calc_variable_is_multi:
if variable.path in objectspace.paths._dynamics and (
identifier is None or identifier[-1] is None
suffix is None or suffix[-1] is None
):
self_dyn_path = objectspace.paths._dynamics.get(self.path)
if self_dyn_path is not None:
@ -465,7 +465,7 @@ class _VariableCalculation(Calculation):
calc_variable_is_multi = True
else:
calc_variable_is_multi = True
elif identifier and "{{ identifier }}" in identifier:
elif suffix and '{{ suffix }}' in suffix:
calc_variable_is_multi = True
if needs_multi:
if calc_variable_is_multi:
@ -479,15 +479,12 @@ class _VariableCalculation(Calculation):
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]
):
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"}}
params[None][0]['index'] = {'index': {'type': 'index'}}
return params
@ -502,14 +499,13 @@ class VariableCalculation(_VariableCalculation):
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, identifier = self.get_variable(objectspace)
variable, suffix = self.get_variable(objectspace)
if not variable and self.optional:
raise VariableCalculationDependencyError()
params = self.get_params(
objectspace,
variable,
identifier,
)
params = self.get_params(objectspace,
variable,
suffix,
)
return {
"function": "calc_value",
"params": params,
@ -525,13 +521,11 @@ class VariablePropertyCalculation(_VariableCalculation):
self,
objectspace,
) -> dict:
variable, identifier = self.get_variable(objectspace)
params = self.get_params(
objectspace,
variable,
identifier,
needs_multi=False,
)
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":
@ -585,7 +579,7 @@ class InformationCalculation(Calculation):
path = self.path
else:
path = self.ori_path
variable, identifier = objectspace.paths.get_with_dynamic(
variable, suffix = objectspace.paths.get_with_dynamic(
self.variable,
self.path_prefix,
path,
@ -593,7 +587,7 @@ class InformationCalculation(Calculation):
self.namespace,
self.xmlfiles,
)
if variable is None or identifier is not None:
if variable is None or suffix is not None:
raise Exception("pfff")
params[None][0]["variable"] = variable
if self.default_values:
@ -604,33 +598,33 @@ class InformationCalculation(Calculation):
}
class _IdentifierCalculation(Calculation):
identifier: Optional[int] = None
class _SuffixCalculation(Calculation):
suffix: Optional[int] = None
def get_identifier(self) -> dict:
identifier = {"type": "identifier"}
if self.identifier is not None:
identifier["identifier"] = self.identifier
return identifier
def get_suffix(self) -> dict:
suffix = {"type": "suffix"}
if self.suffix is not None:
suffix["suffix"] = self.suffix
return suffix
class IdentifierCalculation(_IdentifierCalculation):
class SuffixCalculation(_SuffixCalculation):
attribute_name: Literal["default", "choice", "dynamic"]
def to_function(
self,
objectspace,
) -> dict:
identifier = {"type": "identifier"}
if self.identifier is not None:
identifier["identifier"] = self.identifier
suffix = {"type": "suffix"}
if self.suffix is not None:
suffix["suffix"] = self.suffix
return {
"function": "calc_value",
"params": {None: [self.get_identifier()]},
"params": {None: [self.get_suffix()]},
}
class IdentifierPropertyCalculation(_IdentifierCalculation):
class SuffixPropertyCalculation(_SuffixCalculation):
attribute_name: Literal[*PROPERTY_ATTRIBUTE]
when: Any = undefined
when_not: Any = undefined
@ -644,7 +638,7 @@ class IdentifierPropertyCalculation(_IdentifierCalculation):
raise DictConsistencyError(msg, 105, variable.xmlfiles)
if self.when is not undefined:
if self.when_not is not undefined:
msg = f'the identifier has an invalid attribute "{self.attribute_name}", when and when_not cannot set together'
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
@ -652,13 +646,12 @@ class IdentifierPropertyCalculation(_IdentifierCalculation):
when = self.when_not
inverse = True
else:
msg = f'the identifier has an invalid attribute "{self.attribute_name}", when and when_not cannot set together'
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_identifier()],
"when": when,
"inverse": inverse,
}
params = {None: [self.attribute_name, self.get_suffix()],
"when": when,
"inverse": inverse,
}
return {
"function": "variable_to_property",
"params": params,
@ -686,15 +679,14 @@ CALCULATION_TYPES = {
"jinja": JinjaCalculation,
"variable": VariableCalculation,
"information": InformationCalculation,
"identifier": IdentifierCalculation,
"suffix": IdentifierCalculation,
"suffix": SuffixCalculation,
"index": IndexCalculation,
}
CALCULATION_PROPERTY_TYPES = {
"jinja": JinjaCalculation,
"variable": VariablePropertyCalculation,
"information": InformationCalculation,
"identifier": IdentifierPropertyCalculation,
"suffix": SuffixPropertyCalculation,
"index": IndexCalculation,
}
BASETYPE_CALC = Union[StrictBool, StrictInt, StrictFloat, StrictStr, Calculation, None]
@ -737,7 +729,7 @@ class Variable(BaseModel):
help: Optional[str] = None
hidden: Union[bool, Calculation] = False
disabled: Union[bool, Calculation] = False
mandatory: Union[None, bool, Calculation] = None
mandatory: Union[None, bool, Calculation] = True
empty: Union[None, bool, Calculation] = True
auto_save: bool = False
mode: Optional[str] = None

View file

@ -0,0 +1,41 @@
"""Provider/supplier
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.objspace import get_variables
# from rougail.utils import normalize_family
#
#
# def provider_supplier(objectspace,
# path_prefix,
# ):
# n_path_prefix = normalize_family(path_prefix)
# for variable in get_variables(objectspace):
# if variable.path_prefix != n_path_prefix:
# continue
# if hasattr(variable, 'provider'):
# family_name, variable.name = variable.path.rsplit('.', 1)
# objectspace.paths.set_provider(variable,
# variable.name,
# family_name,
# )
# if hasattr(variable, 'supplier'):
# family_name, variable.name = variable.path.rsplit('.', 1)
# objectspace.paths.set_supplier(variable,
# variable.name,
# family_name,
# )

View file

@ -3,28 +3,28 @@
Silique (https://www.silique.fr)
Copyright (C) 2024
This program is free software: you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by the
Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
distribued with GPL-2 or later license
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 Lesser General Public License for more
details.
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.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
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:
@ -37,9 +37,9 @@ class Annotator(Walk):
if family.commandline:
continue
self.not_for_commandline(family)
not_for_commandlines.append(family.path + ".")
not_for_commandlines.append(family.path + '.')
for variable in self.get_variables():
if variable.type == "symlink":
if variable.type == 'symlink':
continue
variable_path = variable.path
for family_path in not_for_commandlines:
@ -53,60 +53,37 @@ class Annotator(Walk):
self.manage_negative_description(variable)
def not_for_commandline(self, variable) -> None:
self.objectspace.properties.add(variable.path, "not_for_commandline", True)
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 = ""
all_letters = ''
for letter in alternative_name:
all_letters += letter
if all_letters == "h":
msg = _('alternative_name "{0}" conflict with "--help"').format(
alternative_name
)
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 = _('conflict alternative_name "{0}": "{1}" and "{2}"').format(
alternative_name, variable_path, self.alternative_names[all_letters]
)
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:
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,
)
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(
_(
'negative_description is mandatory for boolean variable, but "{0}" hasn\'t'
).format(variable.path),
200,
variable.xmlfiles,
)
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(
_(
'negative_description is only available for boolean variable, but "{0}" is "{1}"'
).format(variable.path, variable.type),
201,
variable.xmlfiles,
)
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
)

View file

@ -4,25 +4,25 @@ Config file for Rougail-structural_commandline
Silique (https://www.silique.fr)
Copyright (C) 2024
This program is free software: you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by the
Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
distribued with GPL-2 or later license
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 Lesser General Public License for more
details.
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.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
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:
def get_rougail_config(*,
backward_compatibility=True,
) -> dict:
options = """
structural_commandline:
description: Configuration rougail-structural_commandline
@ -31,12 +31,12 @@ structural_commandline:
description: Add extra options to tiramisu-cmdline-parser
default: true
"""
return {
"name": "exporter",
"process": "structural",
"options": options,
"level": 20,
}
return {'name': 'exporter',
'process': 'structural',
'options': options,
'level': 20,
}
__all__ = "get_rougail_config"
__all__ = ('get_rougail_config')

View file

@ -3,32 +3,34 @@
Silique (https://www.silique.fr)
Copyright (C) 2024
This program is free software: you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by the
Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
distribued with GPL-2 or later license
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 Lesser General Public License for more
details.
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.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
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
alternative_name: Optional[str]=None
commandline: bool=True
negative_description: Optional[str]=None
class Family(BaseModel):
commandline: bool = True
commandline: bool=True
__all__ = ("Variable", "Family")
__all__ = ('Variable', 'Family')

View file

@ -11,37 +11,33 @@ Copyright (C) 2019-2021
Silique (https://www.silique.fr)
Copyright (C) 2022-2024
This program is free software: you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by the
Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
distribued with GPL-2 or later license
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 Lesser General Public License for more
details.
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.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
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 Any
try:
from tiramisu5 import DynOptionDescription, calc_value
except ModuleNotFoundError:
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 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 import function_waiting_for_error
from tiramisu.error import ValueWarning, ConfigError, PropertiesOptionError
from tiramisu.error import ValueWarning, ConfigError
from .utils import normalize_family
@ -49,55 +45,17 @@ global func
dict_env = {}
ENV = SandboxedEnvironment(loader=DictLoader(dict_env), undefined=StrictUndefined)
func = ENV.filters
ENV.compile_templates("jinja_caches", zip=None)
class JinjaError:
__slot__ = ("_err",)
def __init__(self, err):
self._err = err
def __str__(self):
raise self._err from self._err
def __repr__(self):
raise self._err from self._err
def __eq__(self, *args, **kwargs):
raise self._err from self._err
def __ge__(self, *args, **kwargs):
raise self._err from self._err
def __gt__(self, *args, **kwargs):
raise self._err from self._err
def __le__(self, *args, **kwargs):
raise self._err from self._err
def __lt__(self, *args, **kwargs):
raise self._err from self._err
def __ne__(self, *args, **kwargs):
raise self._err from self._err
def test_propertyerror(value: Any) -> bool:
return isinstance(value, JinjaError)
ENV.tests["propertyerror"] = test_propertyerror
ENV.compile_templates('jinja_caches', zip=None)
def load_functions(path):
global _SourceFileLoader, _spec_from_loader, _module_from_spec, func
loader = _SourceFileLoader("func", path)
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("_"):
if function.startswith('_'):
continue
func[function] = getattr(func_, function)
@ -109,61 +67,41 @@ def rougail_calc_value(*args, __default_value=None, **kwargs):
return values
@function_waiting_for_error
def jinja_to_function(
__internal_variable,
__internal_attribute,
__internal_jinja,
__internal_type,
__internal_multi,
__internal_files,
__default_value=None,
**kwargs,
):
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 isinstance(value, PropertiesOptionError):
value = JinjaError(value)
if "." in key:
if '.' in key:
c_kw = kw
path, var = key.rsplit(".", 1)
for subkey in path.split("."):
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'
)
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)
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 != ""]
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
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 isinstance(value, PropertiesOptionError):
raise value from value
if inverse:
is_match = value != when
else:
@ -171,71 +109,64 @@ def variable_to_property(prop, value, when, inverse):
return prop if is_match else None
@function_waiting_for_error
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)
value = func['jinja_to_function'](**kwargs)
return func['variable_to_property'](prop, value is not None, when, inverse)
@function_waiting_for_error
def jinja_to_property_help(prop, **kwargs):
value = func["jinja_to_function"](**kwargs)
return (prop, f'"{prop}" ({value})')
value = func['jinja_to_function'](**kwargs)
return (prop, f'\"{prop}\" ({value})')
@function_waiting_for_error
def valid_with_jinja(warnings_only=False, **kwargs):
global ValueWarning
value = func["jinja_to_function"](**kwargs)
value = func['jinja_to_function'](**kwargs)
if value:
if warnings_only:
raise ValueWarning(value)
else:
raise ValueError(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
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):
"""Identifier could be an integer, we should convert it in str
Identifier could also contain invalid character, so we should "normalize" it
"""Suffix could be an integer, we should convert it in str
Suffix could also contain invalid character, so we should "normalize" it
"""
def convert_identifier_to_path(self, identifier):
if identifier is None:
return identifier
if not isinstance(identifier, str):
identifier = str(identifier)
return normalize_family(identifier)
def convert_suffix_to_path(self, suffix):
if suffix is None:
return suffix
if not isinstance(suffix, str):
suffix = str(suffix)
return normalize_family(suffix)
def impl_getname(
self,
identifier=None,
suffix=None,
) -> str:
"""get name"""
name = super().impl_getname(None)
if identifier is None:
if suffix is None:
return name
path_identifier = self.convert_identifier_to_path(identifier)
if "{{ identifier }}" in name:
return name.replace("{{ identifier }}", path_identifier)
return name + path_identifier
path_suffix = self.convert_suffix_to_path(suffix)
if "{{ suffix }}" in name:
return name.replace("{{ suffix }}", path_suffix)
return name + path_suffix
def impl_get_display_name(
self,
subconfig,
with_quote: bool = False,
) -> str:
display = super().impl_get_display_name(subconfig, with_quote=with_quote)
if "{{ identifier }}" in display:
return display.replace(
"{{ identifier }}",
self.convert_identifier_to_path(self.get_identifiers(subconfig)[-1]),
)
) -> 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

View file

@ -12,20 +12,22 @@ Copyright (C) 2019-2021
Silique (https://www.silique.fr)
Copyright (C) 2022-2024
This program is free software: you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by the
Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
distribued with GPL-2 or later license
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 Lesser General Public License for more
details.
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.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
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, Union
from json import dumps
from os.path import isfile, basename
@ -87,15 +89,6 @@ class TiramisuReflector:
continue
self.text["header"].append(f"load_functions('{funcs_path}')")
if self.objectspace.export_with_import:
if objectspace.main_namespace:
self.text["header"].extend(
[
"try:",
" groups.namespace",
"except:",
" groups.addgroup('namespace')",
]
)
for mode in self.objectspace.modes_level:
self.text["header"].append(f'ALLOWED_LEADER_PROPERTIES.add("{mode}")')
self.make_tiramisu_objects()
@ -113,9 +106,9 @@ class TiramisuReflector:
def make_tiramisu_objects(self) -> None:
"""make tiramisu objects"""
baseelt = BaseElt()
self.objectspace.reflector_names[baseelt.path] = (
f"option_0{self.objectspace.suffix}"
)
self.objectspace.reflector_names[
baseelt.path
] = f"option_0{self.objectspace.suffix}"
basefamily = Family(
baseelt,
self,
@ -329,19 +322,19 @@ class Common:
information_variable.informations.append(information_name)
return information_name
return f'ParamInformation("{param["information"]}", {default})'
if param["type"] == "identifier":
if "identifier" in param and param["identifier"] != None:
return f"ParamIdentifier(identifier_index={param['identifier']})"
return "ParamIdentifier()"
if param["type"] == "suffix":
if "suffix" in param and param["suffix"] != None:
return f"ParamSuffix(suffix_index={param['suffix']})"
return "ParamSuffix()"
if param["type"] == "index":
return "ParamIndex()"
if param["type"] == "variable":
return self.build_option_param(
param["variable"],
param.get("propertyerror", True),
param.get("identifier"),
param.get("suffix"),
param.get("dynamic"),
param.get("whole", False),
param.get('whole', False),
)
if param["type"] == "any":
if isinstance(param["value"], str):
@ -355,7 +348,7 @@ class Common:
self,
variable,
propertyerror,
identifier: Optional[str],
suffix: Optional[str],
dynamic,
whole: bool,
) -> str:
@ -369,14 +362,9 @@ class Common:
self.calls, self.elt.path
)
params = [f"{option_name}"]
if identifier is not None:
if suffix is not None:
param_type = "ParamDynOption"
identifiers = []
for ident in identifier:
if isinstance(ident, str):
ident = self.convert_str(ident)
identifiers.append(str(ident))
params.append("[" + ", ".join(identifiers) + "]")
params.append(self.convert_str(suffix))
else:
param_type = "ParamOption"
if not propertyerror:
@ -407,6 +395,7 @@ class Common:
ret += ", kwargs={" + ", ".join(kwargs) + "}"
ret += ")"
if hasattr(child, "warnings_only"):
print("HU????")
ret += f", warnings_only={child.warnings_only}"
if "help" in child:
ret += f", help_function=func['{child['help']}']"
@ -476,15 +465,13 @@ class Variable(Common):
keys["values"] = self.populate_calculation(
self.elt.choices, return_a_tuple=True
)
if self.elt.type == "regexp":
self.object_type = "Regexp_" + self.option_name
self.tiramisu.text["header"].append(
f"""class {self.object_type}(RegexpOption):
if self.elt.type == 'regexp':
self.object_type = 'Regexp_' + self.option_name
self.tiramisu.text['header'].append(f'''class {self.object_type}(RegexpOption):
__slots__ = tuple()
_type = 'value'
{self.object_type}._regexp = re_compile(r"{self.elt.regexp}")
"""
)
''')
if self.elt.path in self.objectspace.multis:
keys["multi"] = self.objectspace.multis[self.elt.path]
if hasattr(self.elt, "default") and self.elt.default is not None:
@ -531,10 +518,6 @@ class Family(Common):
self.object_type = "Leadership"
else:
self.object_type = "OptionDescription"
if hasattr(self.elt, "name") and self.elt.name == self.elt.namespace:
self.group_type = "groups.namespace"
else:
self.group_type = None
self.children = []
def add(self, child):
@ -545,10 +528,8 @@ class Family(Common):
self,
keys: list,
) -> None:
if self.group_type:
keys["group_type"] = self.group_type
if self.elt.type == "dynamic":
keys["identifiers"] = self.populate_calculation(self.elt.dynamic)
keys["suffixes"] = self.populate_calculation(self.elt.dynamic)
children = []
for path in self.objectspace.parents[self.elt.path]:
children.append(self.objectspace.paths[path])

View file

@ -2,18 +2,20 @@
Silique (https://www.silique.fr)
Copyright (C) 2024
This program is free software: you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by the
Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
distribued with GPL-2 or later license
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 Lesser General Public License for more
details.
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.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
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

View file

@ -6,18 +6,21 @@ Copyright (C) 2021
Silique (https://www.silique.fr)
Copyright (C) 2022-2024
This program is free software: you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by the
Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
distribued with GPL-2 or later license
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 Lesser General Public License for more
details.
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.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
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 os import listdir
@ -60,7 +63,7 @@ class upgrade_010_to_10:
xmlsrc: str,
) -> None:
self.xmlsrc = xmlsrc
self.paths = {"family": {}, "variable": {}, "dynamic": {}}
self.paths = {"family": {}, "variable": {}, 'dynamic': {}}
self.lists = {
"service": {},
"ip": {},
@ -79,26 +82,18 @@ class upgrade_010_to_10:
sub_path: str,
true_sub_path: str,
*,
root: bool = False,
is_dynamic: bool = False,
root: bool=False,
is_dynamic: bool=False,
) -> dict:
new_families = {}
if root:
new_families["version"] = None
new_families['version'] = None
if "variables" in family:
for subelt in family["variables"]:
for typ, obj in subelt.items():
for subobj in obj:
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,
)
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")
return new_families
@ -117,7 +112,7 @@ class upgrade_010_to_10:
else:
true_sub_path = name
if is_dynamic:
name += "{{ suffix }}"
name += '{{ suffix }}'
if sub_path:
sub_path = sub_path + "." + name
else:
@ -131,9 +126,7 @@ class upgrade_010_to_10:
if typ == "dynamic":
family["variable"] = self.get_variable_path(value)
# add sub families and sub variables
sub_families = self.parse_variables(
family, sub_path, true_sub_path, is_dynamic=is_dynamic
)
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():
if sub_name not in family:
continue
@ -159,13 +152,13 @@ class upgrade_010_to_10:
else:
true_sub_path = name
self.flatten_paths["variable"][name] = true_sub_path
name += "{{ suffix }}"
name += '{{ suffix }}'
if sub_path:
sub_path = sub_path + "." + name
else:
sub_path = name
if is_dynamic:
self.paths["dynamic"][true_sub_path] = sub_path
self.paths['dynamic'][true_sub_path] = sub_path
new_families[name] = variable
self.flatten_paths["variable"][name] = sub_path
self.paths["variable"][sub_path] = variable
@ -205,18 +198,17 @@ class upgrade_010_to_10:
)(test)
)
variable["test"] = tests
if variable.get("mandatory", False):
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"]]
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):
for variable in self.paths["variable"].values():
multi = variable.get("multi", False)
multi = variable.get('multi', False)
if "value" in variable:
default = variable.pop("value")
if default is not None:
@ -231,8 +223,7 @@ class upgrade_010_to_10:
variable["choices"] = variable.pop("choice")
else:
variable["choices"] = [
self.get_value(choice, multi)
for choice in variable.pop("choice")
self.get_value(choice, multi) for choice in variable.pop("choice")
]
def parse_services(
@ -359,21 +350,21 @@ class upgrade_010_to_10:
if variable_path is None:
continue
variable = self.paths["variable"][variable_path]
if variable.get("multi", False):
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"
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)
condition_value.setdefault('params', {}).update(force_param)
for target in condition["target"]:
typ = target.get("type", "variable")
if typ == "variable":
@ -426,9 +417,7 @@ class upgrade_010_to_10:
check["param"] = [
{"text": variable_path, "type": "variable"}
] + check.get("param", [])
check_value = self.convert_param_function(
check, variable.get("multi", False)
)
check_value = self.convert_param_function(check, variable.get('multi', False))
variable.setdefault("validators", []).append(check_value)
def parse_fill(
@ -448,16 +437,12 @@ class upgrade_010_to_10:
"jinja": fill["name"],
}
else:
fill_value = self.convert_param_function(
fill, variable.get("multi", False)
)
fill_value = self.convert_param_function(fill, variable.get('multi', False))
variable["default"] = fill_value
if variable.get("mandatory") is False:
del variable["mandatory"]
if variable.get('mandatory') is False:
del variable['mandatory']
else:
raise Exception(
f'cannot set fill to unknown variable "{variable_path}"'
)
raise Exception(f'cannot set fill to unknown variable "{variable_path}"')
def params_condition_to_jinja(
self,
@ -553,8 +538,8 @@ class upgrade_010_to_10:
new_param = {attr_name: value}
value = attr_name
elif typ in ["index", "suffix"]:
if "name" in value:
attr_name = value["name"]
if 'name' in value:
attr_name = value['name']
else:
attr_name = f"__{typ}"
new_param = {attr_name: {"type": typ}}
@ -565,7 +550,7 @@ class upgrade_010_to_10:
value = value[typ]
elif "{{ suffix }}" in value[typ]:
path = value[typ]
attr_name = path.split(".")[-1][:-12] # remove {{ suffix }}
attr_name = path.split('.')[-1][:-12] # remove {{ suffix }}
new_param = {attr_name: value}
value = attr_name
else:
@ -583,22 +568,16 @@ class upgrade_010_to_10:
) -> str:
text = param["name"]
params = {}
# multi = False
# multi = False
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")
):
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"]
if 'optional' in param["param"][0]:
ret['optional'] = param["param"][0]["optional"]
return ret
first, *others = param["param"]
new_param, first = self.get_jinja_param_and_value(first, multi)
@ -625,13 +604,9 @@ class upgrade_010_to_10:
if not multi:
text = "{{ " + text + " }}"
else:
text = (
"""{% for __variable in """
+ text
+ """ %}
text = """{% for __variable in """ + text + """ %}
{{ __variable }}
{% endfor %}"""
)
ret = {"type": "jinja", "jinja": text}
if params:
ret["params"] = params
@ -684,21 +659,16 @@ class RougailUpgrade:
def run(
self,
):
for dict_dir, dest_dir in zip(
self.rougailconfig["main_dictionaries"],
self.rougailconfig["upgrade_options.main_dictionaries"],
):
for dict_dir, dest_dir in zip(self.rougailconfig["main_dictionaries"], self.rougailconfig["upgrade_options.main_dictionaries"]):
self._load_dictionaries(
dict_dir,
dest_dir,
normalize_family(self.rougailconfig["main_namespace"]),
)
if self.rougailconfig["main_namespace"]:
if self.rougailconfig['main_namespace']:
if self.rougailconfig["extra_dictionaries"]:
dst_extra_dir = self.rougailconfig["upgrade_options.extra_dictionary"]
for namespace, extra_dirs in self.rougailconfig[
"extra_dictionaries"
].items():
for namespace, extra_dirs in self.rougailconfig["extra_dictionaries"].items():
extra_dstsubfolder = Path(dst_extra_dir) / namespace
if not extra_dstsubfolder.is_dir():
extra_dstsubfolder.mkdir()
@ -707,7 +677,7 @@ class RougailUpgrade:
str(extra_dir),
str(extra_dstsubfolder),
normalize_family(namespace),
)
)
def _load_dictionaries(
self,
@ -727,7 +697,7 @@ class RougailUpgrade:
for filename in filenames:
xmlsrc = Path(srcfolder) / Path(filename)
ymldst = Path(dstfolder) / (Path(filename).stem + ".yml")
ymldst = Path(dstfolder) / (Path(filename).stem + '.yml')
if filename.endswith(".xml"):
if parse is None:
raise Exception("XML module is not installed")
@ -735,7 +705,7 @@ class RougailUpgrade:
parser = XMLParser(remove_blank_text=True)
document = parse(xmlsrc, parser)
except XMLSyntaxError as err:
raise Exception(_("not a XML file: {0}").format(err)) from err
raise Exception(_(f"not a XML file: {err}")) from err
root = document.getroot()
search_function_name = get_function_name(
root.attrib.get("version", "1")
@ -762,7 +732,7 @@ class RougailUpgrade:
root_services = root_services_
if function_version == search_function_name:
function_found = True
if root != {"version": None}:
if root != {'version': None}:
root["version"] = float(version)
with ymldst.open("w") as ymlfh:
yaml = YAML()
@ -953,26 +923,25 @@ class RougailUpgrade:
"variable": value.pop("variable"),
"propertyerror": False,
}
if "{{ suffix }}" not in key:
new_root[key + "{{ suffix }}"] = new_root.pop(key)
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")
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"]
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"]
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)

View file

@ -11,20 +11,22 @@ Copyright (C) 2019-2021
Silique (https://www.silique.fr)
Copyright (C) 2022-2024
This program is free software: you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by the
Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
distribued with GPL-2 or later license
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 Lesser General Public License for more
details.
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.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
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, Union
from unicodedata import normalize, combining
import re
@ -53,8 +55,9 @@ def valid_variable_family_name(
match = NAME_REGEXP.search(name)
if not match:
msg = _(
'invalid variable or family name "{0}" must only contains lowercase ascii character, number or _'
).format(name)
f'invalid variable or family name "{name}" must only contains '
"lowercase ascii character, number or _"
)
raise DictConsistencyError(msg, 76, xmlfiles)
@ -114,16 +117,14 @@ def get_jinja_variable_to_param(
for g in parsed_content.find_all(Getattr):
variables.add(recurse_getattr(g))
except TemplateSyntaxError as err:
msg = _('error in jinja "{0}" for the variable "{1}": {2}').format(
jinja_text, current_path, err
)
msg = _(f'error in jinja "{jinja_text}" for the variable "{ current_path }": {err}')
raise DictConsistencyError(msg, 39, xmlfiles) from err
variables = list(variables)
variables.sort(reverse=True)
founded_variables = {}
unknown_variables = []
for variable_path in variables:
variable, identifier = objectspace.paths.get_with_dynamic(
variable, suffix = objectspace.paths.get_with_dynamic(
variable_path,
path_prefix,
current_path,
@ -132,9 +133,9 @@ def get_jinja_variable_to_param(
xmlfiles,
)
if variable and variable.path in objectspace.variables:
founded_variables[variable_path] = (identifier, variable)
founded_variables[variable_path] = (suffix, variable)
else:
sub_family = variable_path + "."
sub_family = variable_path + '.'
for founded_variable in chain(founded_variables, unknown_variables):
if founded_variable.startswith(sub_family):
break
@ -148,9 +149,9 @@ def get_jinja_variable_to_param(
else:
root_path = None
vpath = variable_path
while "." in vpath:
vpath = vpath.rsplit(".", 1)[0]
variable, identifier = objectspace.paths.get_with_dynamic(
while '.' in vpath:
vpath = vpath.rsplit('.', 1)[0]
variable, suffix = objectspace.paths.get_with_dynamic(
vpath,
path_prefix,
current_path,

Some files were not shown because too many files have changed in this diff Show more