|
| 1 | +===================== |
| 2 | +Field Declaration DSL |
| 3 | +===================== |
| 4 | + |
| 5 | +.. default-domain:: py |
| 6 | +.. highlight:: python |
| 7 | +.. role:: py(code) |
| 8 | + :language: Python |
| 9 | + |
| 10 | + |
| 11 | +The Field Declaration DSL is a Domain Specific Language (DSL) that used to |
| 12 | +define the type and structure of field values. A DSL declaration consists of |
| 13 | +one or more :term:`modifier`\ s separated by commas (``,``). |
| 14 | + |
| 15 | +Python API |
| 16 | +========== |
| 17 | + |
| 18 | +User can create a :class:`sphinxnotes.render.Field` from DSL and use it to parse |
| 19 | +string to :type:`sphinxnotes.render.Value`: |
| 20 | + |
| 21 | +>>> from sphinxnotes.render import Field |
| 22 | +>>> Field.from_dsl('list of int').parse('1,2,3') |
| 23 | +[1, 2, 3] |
| 24 | + |
| 25 | +Syntax |
| 26 | +====== |
| 27 | + |
| 28 | +.. productionlist:: |
| 29 | + dsl : modifier ("," modifier)* |
| 30 | + modifier : type_modifier | form_modifier | flag | by_option |
| 31 | + |
| 32 | +.. glossary:: |
| 33 | + |
| 34 | + Modifier |
| 35 | + There are four categories of modifiers: |
| 36 | + |
| 37 | + Type modifier |
| 38 | + Specifies the element type (scalar value) |
| 39 | + |
| 40 | + Form modifier |
| 41 | + Specifies a container type with element type |
| 42 | + |
| 43 | + Flag |
| 44 | + A boolean flag (either on or off) |
| 45 | + |
| 46 | + By-Option |
| 47 | + A key-value option |
| 48 | + |
| 49 | +Type |
| 50 | +==== |
| 51 | + |
| 52 | +A type modifier specifies the data type of a single (scalar) value. |
| 53 | + |
| 54 | +.. list-table:: |
| 55 | + :header-rows: 1 |
| 56 | + |
| 57 | + * - Modifier |
| 58 | + - Type |
| 59 | + - Aliases |
| 60 | + - Description |
| 61 | + * - ``bool`` |
| 62 | + - :py:class:`bool` |
| 63 | + - ``flag`` |
| 64 | + - Boolean: ``true``/``yes``/``1``/``on``/``y`` → True, ``false``/``no``/``0``/``off``/``n`` → False |
| 65 | + * - ``int`` |
| 66 | + - :py:class:`int` |
| 67 | + - ``integer`` |
| 68 | + - Integer |
| 69 | + * - ``float`` |
| 70 | + - :py:class:`float` |
| 71 | + - ``number``, ``num`` |
| 72 | + - Floating-point number |
| 73 | + * - ``str`` |
| 74 | + - :py:class:`str` |
| 75 | + - ``string`` |
| 76 | + - String. If looks like a Python literal (e.g., ``"hello"``), it's parsed accordingly. |
| 77 | + |
| 78 | +Examples: |
| 79 | + |
| 80 | +======= ========= ============= |
| 81 | +DSL Input Result |
| 82 | +------- --------- ------------- |
| 83 | +``int`` ``42`` :py:`42` |
| 84 | +``str`` ``hello`` :py:`"hello"` |
| 85 | +======= ========= ============= |
| 86 | + |
| 87 | +Form |
| 88 | +==== |
| 89 | + |
| 90 | +A form modifier specifies a container type with its element type, using |
| 91 | +``<form> of <type>`` syntax. |
| 92 | + |
| 93 | +.. list-table:: |
| 94 | + :header-rows: 1 |
| 95 | + |
| 96 | + * - Modifier |
| 97 | + - Container |
| 98 | + - Separator |
| 99 | + - Description |
| 100 | + * - ``list of <type>`` |
| 101 | + - :py:class:`list` |
| 102 | + - ``,`` |
| 103 | + - Comma-separated list |
| 104 | + * - ``lines of <type>`` |
| 105 | + - :py:class:`list` |
| 106 | + - ``\n`` |
| 107 | + - Newline-separated list |
| 108 | + * - ``words of <type>`` |
| 109 | + - :py:class:`list` |
| 110 | + - whitespace |
| 111 | + - Whitespace-separated list |
| 112 | + * - ``set of <type>`` |
| 113 | + - :py:class:`set` |
| 114 | + - whitespace |
| 115 | + - Whitespace-separated set (unique values) |
| 116 | + |
| 117 | +Examples: |
| 118 | + |
| 119 | +================ =========== ===================== |
| 120 | +DSL Input Result |
| 121 | +---------------- ----------- --------------------- |
| 122 | +``list of int`` ``1, 2, 3`` :py:`[1, 2, 3]` |
| 123 | +``lines of str`` ``a\nb`` :py:`['a', 'b']` |
| 124 | +``words of str`` ``a b c`` :py:`['a', 'b', 'c']` |
| 125 | +================ =========== ===================== |
| 126 | + |
| 127 | +Flag |
| 128 | +==== |
| 129 | + |
| 130 | +A flag is a boolean modifier that can be either on or off. |
| 131 | + |
| 132 | +Every flag is available as a attribute of the :class:`Field`. |
| 133 | +For example, we have a "required" flag registed, we can access ``Field.required`` |
| 134 | +attribute. |
| 135 | + |
| 136 | +.. list-table:: |
| 137 | + :header-rows: 1 |
| 138 | + |
| 139 | + * - Modifier |
| 140 | + - Aliases |
| 141 | + - Default |
| 142 | + - Description |
| 143 | + * - ``required`` |
| 144 | + - ``require``, ``req`` |
| 145 | + - ``False`` |
| 146 | + - Field must have a value |
| 147 | + |
| 148 | +Examples:: |
| 149 | + |
| 150 | + int, required |
| 151 | + |
| 152 | +By-Option |
| 153 | +========= |
| 154 | + |
| 155 | +A by-option is a key-value modifier with the syntax ``<name> by <value>``. |
| 156 | + |
| 157 | +Every by-option is available as a attribute of the :class:`Field`. |
| 158 | +For example, we have a "sep" flag registed, we can get the value of separator |
| 159 | +from ``Field.sep`` attribute. |
| 160 | + |
| 161 | +Built-in by-options: |
| 162 | + |
| 163 | +.. list-table:: |
| 164 | + :header-rows: 1 |
| 165 | + |
| 166 | + * - Modifier |
| 167 | + - Type |
| 168 | + - Description |
| 169 | + * - ``sep by '<sep>'`` |
| 170 | + - :py:class:`str` |
| 171 | + - Custom separator for value form. Implies ``list`` if no form specified. |
| 172 | + |
| 173 | +Examples: |
| 174 | + |
| 175 | +=================== ========= ================ |
| 176 | +DSL Input Result |
| 177 | +------------------- --------- ---------------- |
| 178 | +``str, sep by '|'`` ``a|b`` :py:`['a', 'b']` |
| 179 | +``int, sep by ':'`` ``1:2:3`` :py:`[1, 2, 3]` |
| 180 | +=================== ========= ================ |
| 181 | + |
| 182 | +Extending the DSL |
| 183 | +================= |
| 184 | + |
| 185 | +You can extend the DSL by registering custom types, flags, and by-options |
| 186 | +through the :attr:`~sphinxnotes.render.Registry.data` attribute of |
| 187 | +:data:`sphinxnotes.render.REGISTRY`. |
| 188 | + |
| 189 | +.. _add-custom-types: |
| 190 | + |
| 191 | +Adding Custom Types |
| 192 | +------------------- |
| 193 | + |
| 194 | +Use :meth:`~sphinxnotes.render.data.REGISTRY.add_type` method of |
| 195 | +:data:`sphinxnotes.render.REGISTRY` to add a new type: |
| 196 | + |
| 197 | +>>> from sphinxnotes.render import REGISTRY |
| 198 | +>>> |
| 199 | +>>> def parse_color(v: str): |
| 200 | +... return tuple(int(x) for x in v.split(';')) |
| 201 | +... |
| 202 | +>>> def color_to_str(v): |
| 203 | +... return ';'.join(str(x) for x in v) |
| 204 | +... |
| 205 | +>>> REGISTRY.data.add_type('color', tuple, parse_color, color_to_str) |
| 206 | +>>> Field.from_dsl('color').parse('255;0;0') |
| 207 | +(255, 0, 0) |
| 208 | + |
| 209 | +.. _add-custom-flags: |
| 210 | + |
| 211 | +Adding Custom Flags |
| 212 | +------------------- |
| 213 | + |
| 214 | +Use :meth:`~sphinxnotes.render.data.Registry.add_flag` method of |
| 215 | +:data:`sphinxnotes.render.REGISTRY` to add a new type: |
| 216 | + |
| 217 | +>>> from sphinxnotes.render import REGISTRY |
| 218 | +>>> REGISTRY.data.add_flag('unique', default=False) |
| 219 | +>>> field = Field.from_dsl('int, unique') |
| 220 | +>>> field.unique |
| 221 | +True |
| 222 | + |
| 223 | +.. _add-custom-by-options: |
| 224 | + |
| 225 | +Adding Custom By-Options |
| 226 | +------------------------ |
| 227 | + |
| 228 | +Use :meth:`~sphinxnotes.render.data.Registry.add_by_option` method of |
| 229 | +:data:`sphinxnotes.render.REGISTRY` to add a new by-option: |
| 230 | + |
| 231 | +>>> from sphinxnotes.render import REGISTRY |
| 232 | +>>> REGISTRY.data.add_by_option('group', str) |
| 233 | +>>> field = Field.from_dsl('str, group by size') |
| 234 | +>>> field.group |
| 235 | +'size' |
| 236 | +>>> REGISTRY.data.add_by_option('index', str, store='append') |
| 237 | +>>> field = Field.from_dsl('str, index by month, index by year') |
| 238 | +>>> field.index |
| 239 | +['month', 'year'] |
0 commit comments