Skip to content

Commit 4f33753

Browse files
authored
Merge pull request #6 from sphinx-notes/add-dsl-docs
docs: Add DSL documentation
2 parents 04ae980 + 6cbc37b commit 4f33753

10 files changed

Lines changed: 377 additions & 17 deletions

File tree

.cruft.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"template": "https://github.com/sphinx-notes/cookiecutter",
3-
"commit": "634c4022e575bd086ea47f3b42feafe24e14a939",
3+
"commit": "62cd96195962da3392cdc34125c95e9144a5f5ca",
44
"checkout": null,
55
"context": {
66
"cookiecutter": {
@@ -20,7 +20,7 @@
2020
"sphinx_version": "7.0",
2121
"development_status": "3 - Alpha",
2222
"_template": "https://github.com/sphinx-notes/cookiecutter",
23-
"_commit": "634c4022e575bd086ea47f3b42feafe24e14a939"
23+
"_commit": "62cd96195962da3392cdc34125c95e9144a5f5ca"
2424
}
2525
},
2626
"directory": null

.github/workflows/test.yml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,14 @@ jobs:
1212
- uses: actions/setup-python@v5
1313
with:
1414
python-version-file: 'pyproject.toml'
15-
- run: python3 -m pip install .[dev]
15+
- run: python3 -m pip install .[test]
1616
- run: make test
17+
doctest:
18+
runs-on: ubuntu-latest
19+
steps:
20+
- uses: actions/checkout@v2
21+
- uses: actions/setup-python@v5
22+
with:
23+
python-version-file: 'pyproject.toml'
24+
- run: python3 -m pip install .[docs]
25+
- run: make doctest

Makefile

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,11 @@ fmt:
2727

2828
.PHONY: test
2929
test:
30-
$(PY) -m unittest discover -s tests -v
30+
$(PY) -m pytest tests/ -v
31+
32+
.PHONY: doctest
33+
doctest:
34+
$(MAKE) doctest -C docs/
3135

3236
################################################################################
3337
# Distribution Package

docs/api.rst

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,66 @@
22
API Reference
33
=============
44

5-
.. note:: WIP
5+
Data Types
6+
==========
67

7-
.. automodule:: sphinxnotes.render.pipeline
8+
.. autotype:: sphinxnotes.render.PlainValue
9+
.. autotype:: sphinxnotes.render.Value
10+
11+
.. autoclass:: sphinxnotes.render.RawData
12+
.. autoclass:: sphinxnotes.render.ParsedData
13+
14+
.. autoclass:: sphinxnotes.render.Field
15+
.. autoclass:: sphinxnotes.render.Schema
16+
17+
.. autoclass:: sphinxnotes.render.data.Registry
18+
19+
.. automethod:: add_type
20+
.. automethod:: add_form
21+
.. automethod:: add_flag
22+
.. automethod:: add_by_option
23+
24+
.. autotype:: sphinxnotes.render.data.ByOptionStore
25+
26+
The Render Pipeline
27+
===================
28+
29+
Context
30+
-------
31+
32+
.. autoclass:: sphinxnotes.render.PendingContext
33+
.. autotype:: sphinxnotes.render.ResolvedContext
34+
.. autoclass:: sphinxnotes.render.UnparsedData
35+
36+
.. autoclass:: sphinxnotes.render.pending_node
37+
38+
Extra Context
39+
-------------
40+
41+
.. autoclass:: sphinxnotes.render.ExtraContextGenerator
42+
.. autoclass:: sphinxnotes.render.ExtraContextRegistry
43+
44+
Template
45+
--------
46+
47+
.. autoclass:: sphinxnotes.render.Template
48+
.. autoclass:: sphinxnotes.render.Phase
49+
50+
Pipeline
51+
--------
52+
53+
.. autoclass:: sphinxnotes.render.BaseContextRole
54+
.. autoclass:: sphinxnotes.render.BaseContextDirective
55+
.. autoclass:: sphinxnotes.render.BaseDataDefineRole
56+
.. autoclass:: sphinxnotes.render.BaseDataDefineDirective
57+
.. autoclass:: sphinxnotes.render.StrictDataDefineDirective
58+
59+
Registry
60+
========
61+
62+
.. autodata:: sphinxnotes.render.REGISTRY
63+
64+
.. autoclass:: sphinxnotes.render.Registry
65+
66+
.. autoproperty:: data
67+
.. autoproperty:: extra_context

docs/conf.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
# ones.
2424
extensions = [
2525
'sphinx.ext.githubpages',
26+
'sphinx.ext.doctest',
2627
'sphinx_design',
2728
'sphinx_copybutton',
2829
'sphinx_last_updated_by_git',
@@ -124,3 +125,5 @@
124125

125126
_ = extensions.pop() # no need to load extension
126127
primary_domain = 'py'
128+
129+
extensions.append('sphinx.ext.doctest')

docs/dsl.rst

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
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']

docs/index.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ Getting Started
4343
.. ADDITIONAL CONTENT START
4444
4545
This extension is not intended to be used directly by Sphinx user.
46-
It is for Sphinx extension developer, please refer to :doc:`api`.
46+
It is for Sphinx extension developer, please refer to :doc:`dsl` and :doc:`api`.
4747

4848
.. ADDITIONAL CONTENT END
4949
@@ -53,6 +53,7 @@ Contents
5353
.. toctree::
5454
:caption: Contents
5555

56+
dsl
5657
api
5758
changelog
5859

0 commit comments

Comments
 (0)