talika 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. talika-0.1.0/.gitignore +10 -0
  2. talika-0.1.0/CHANGELOG.md +42 -0
  3. talika-0.1.0/CONTRIBUTING.md +37 -0
  4. talika-0.1.0/LICENSE +21 -0
  5. talika-0.1.0/PKG-INFO +433 -0
  6. talika-0.1.0/README.md +399 -0
  7. talika-0.1.0/pyproject.toml +111 -0
  8. talika-0.1.0/src/talika/__init__.py +132 -0
  9. talika-0.1.0/src/talika/__main__.py +15 -0
  10. talika-0.1.0/src/talika/annotations.py +168 -0
  11. talika-0.1.0/src/talika/checker.py +286 -0
  12. talika-0.1.0/src/talika/cli.py +382 -0
  13. talika-0.1.0/src/talika/context.py +127 -0
  14. talika-0.1.0/src/talika/dsl.py +508 -0
  15. talika-0.1.0/src/talika/errors.py +332 -0
  16. talika-0.1.0/src/talika/fields.py +487 -0
  17. talika-0.1.0/src/talika/group_expansion.py +624 -0
  18. talika-0.1.0/src/talika/introspection.py +238 -0
  19. talika-0.1.0/src/talika/parsers.py +616 -0
  20. talika-0.1.0/src/talika/parsing.py +90 -0
  21. talika-0.1.0/src/talika/py.typed +1 -0
  22. talika-0.1.0/src/talika/pytest_plugin.py +108 -0
  23. talika-0.1.0/src/talika/records.py +165 -0
  24. talika-0.1.0/src/talika/schema.py +2040 -0
  25. talika-0.1.0/src/talika/sources.py +96 -0
  26. talika-0.1.0/src/talika/table.py +236 -0
  27. talika-0.1.0/src/talika/transformers.py +162 -0
  28. talika-0.1.0/tests/data/invalid_users.feature +6 -0
  29. talika-0.1.0/tests/test_annotations.py +83 -0
  30. talika-0.1.0/tests/test_cell_dsl.py +156 -0
  31. talika-0.1.0/tests/test_checker.py +227 -0
  32. talika-0.1.0/tests/test_column_table.py +89 -0
  33. talika-0.1.0/tests/test_composition.py +121 -0
  34. talika-0.1.0/tests/test_custom_parsers.py +50 -0
  35. talika-0.1.0/tests/test_defaults_aliases_policies.py +145 -0
  36. talika-0.1.0/tests/test_error_collection.py +108 -0
  37. talika-0.1.0/tests/test_errors.py +40 -0
  38. talika-0.1.0/tests/test_field_components.py +35 -0
  39. talika-0.1.0/tests/test_fields.py +93 -0
  40. talika-0.1.0/tests/test_functional_api.py +80 -0
  41. talika-0.1.0/tests/test_group_expansion.py +270 -0
  42. talika-0.1.0/tests/test_introspection.py +32 -0
  43. talika-0.1.0/tests/test_output_factory.py +20 -0
  44. talika-0.1.0/tests/test_output_models.py +75 -0
  45. talika-0.1.0/tests/test_package_metadata.py +13 -0
  46. talika-0.1.0/tests/test_parsers.py +85 -0
  47. talika-0.1.0/tests/test_pydantic_output.py +34 -0
  48. talika-0.1.0/tests/test_pytest_plugin.py +18 -0
  49. talika-0.1.0/tests/test_record_sources.py +42 -0
  50. talika-0.1.0/tests/test_record_validation.py +119 -0
  51. talika-0.1.0/tests/test_references.py +104 -0
  52. talika-0.1.0/tests/test_row_table.py +92 -0
  53. talika-0.1.0/tests/test_table_data.py +98 -0
  54. talika-0.1.0/tests/test_table_transform.py +129 -0
  55. talika-0.1.0/tests/test_table_validation.py +62 -0
  56. talika-0.1.0/tests/test_variants.py +388 -0
  57. talika-0.1.0/tests/typing/public_api.py +38 -0
@@ -0,0 +1,10 @@
1
+ .pytest_cache/
2
+ .ruff_cache/
3
+ .uv-cache/
4
+ .uv-python/
5
+ .venv/
6
+ .runner-debug/
7
+ __pycache__/
8
+ *.py[cod]
9
+ dist/
10
+ docs_site/
@@ -0,0 +1,42 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. Until the first
4
+ stable release, additions may refine APIs while preserving the documented
5
+ `0.1` behavior whenever practical.
6
+
7
+ ## 0.1.0
8
+
9
+ The first public alpha release of Talika introduces typed, validated contracts
10
+ for BDD data tables without imposing a project-specific table vocabulary.
11
+
12
+ ### Schemas and fields
13
+
14
+ - Parse both row-oriented and column-oriented data tables into typed schema records.
15
+ - Declare required, optional, aliased, and identifier fields with static or context-aware defaults.
16
+ - Infer converters from scalar, optional, enum, and string-literal type annotations.
17
+ - Control empty cells independently from missing fields with preserve, parse, null, and forbid policies.
18
+ - Compose reusable field groups and discriminated variants for tables containing multiple record shapes.
19
+ - Resolve single and multi-value references between records, including identifiers with custom parsers.
20
+
21
+ ### Parsing and extension points
22
+
23
+ - Convert cells with built-in string, integer, float, decimal, boolean, choice, mapping, split, optional, compose, and collection parsers.
24
+ - Build project-owned cell languages from scoped tokens, regular-expression patterns, predicates, fallbacks, and composable `CellDSL` instances.
25
+ - Transform tables before parsing with custom hooks or left-to-right transformer pipelines while preserving original source locations.
26
+ - Expand compact column groups with numeric or alphabetic ranges and configurable prefix or suffix repetition rules.
27
+ - Return schema records, dataclasses, Pydantic models, keyword-constructed classes, or objects produced by custom output builders.
28
+
29
+ ### Validation and diagnostics
30
+
31
+ - Validate individual records and complete record collections after conversion, defaults, variants, and references are resolved.
32
+ - Report table shape, field, parser, transformation, reference, validation, and output errors with stable codes and source coordinates.
33
+ - Choose fail-fast behavior or collect independent failures into an ordered `TableErrors` aggregate.
34
+ - Inspect immutable record and field source metadata, including row, column, item identifier, and original transformed cells.
35
+
36
+ ### Integrations and tooling
37
+
38
+ - Parse tables in pytest-bdd steps through the registered `talika` fixture or the functional parsing API.
39
+ - Discover and statically validate Gherkin data tables through the Python checker API or the `talika check` command.
40
+ - Emit human-readable or JSON checker diagnostics and supply deterministic project context through CLI context factories.
41
+ - Inspect complete field and variant contracts through `describe()`, `talika describe`, or JSON output.
42
+ - Ship a dependency-free typed core for Python 3.10 through 3.13 with optional CLI, Pydantic, and testing extras.
@@ -0,0 +1,37 @@
1
+ # Contributing
2
+
3
+ ## Development setup
4
+
5
+ The project uses Python 3.10 or newer and `uv` for reproducible environments:
6
+
7
+ ```powershell
8
+ uv sync --all-extras --dev
9
+ uv run pytest -p no:cacheprovider
10
+ uv run ruff check .
11
+ uv run ruff format --check .
12
+ uv run mypy src tests/typing/public_api.py
13
+ uv build
14
+ ```
15
+
16
+ ## Design principles
17
+
18
+ - Keep business actions and domain vocabulary outside the core package.
19
+ - Prefer small schema declarations over a general grammar framework.
20
+ - Preserve original feature-file locations through every transformation.
21
+ - Keep direct schema parsing independent from pytest and pytest-bdd.
22
+ - Make extension contracts explicit and test custom implementations.
23
+ - Add focused tests and documentation for every user-facing capability.
24
+ - Preserve missing-field versus explicitly-empty-cell behavior.
25
+
26
+ ## Tests
27
+
28
+ Changes should include focused unit tests and, for user-facing behavior,
29
+ documentation under `docs/`. Test error messages through structured attributes
30
+ and stable codes where possible rather than relying only on complete strings.
31
+
32
+ ## Compatibility
33
+
34
+ The supported baseline is Python 3.10+, pytest 8+, and pytest-bdd 8+. CI checks
35
+ multiple Python and pytest releases. Deprecations should be documented before
36
+ removal, and the package version must be changed only as part of an explicit
37
+ release decision.
talika-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 talika contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
talika-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,433 @@
1
+ Metadata-Version: 2.4
2
+ Name: talika
3
+ Version: 0.1.0
4
+ Summary: Typed, validated schemas for BDD data tables
5
+ Project-URL: Documentation, https://talikadev.github.io/talika/
6
+ Project-URL: Source, https://github.com/talikadev/talika
7
+ Project-URL: Issues, https://github.com/talikadev/talika/issues
8
+ Project-URL: Changelog, https://github.com/talikadev/talika/blob/master/CHANGELOG.md
9
+ Author: Chinmay Singh, Nishant Bhandari
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: bdd,gherkin,pytest,pytest-bdd,testing
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Framework :: Pytest
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Typing :: Typed
23
+ Requires-Python: >=3.10
24
+ Provides-Extra: cli
25
+ Requires-Dist: gherkin-official>=29.0; extra == 'cli'
26
+ Provides-Extra: pydantic
27
+ Requires-Dist: pydantic>=2.0; extra == 'pydantic'
28
+ Provides-Extra: test
29
+ Requires-Dist: gherkin-official>=29.0; extra == 'test'
30
+ Requires-Dist: pydantic>=2.0; extra == 'test'
31
+ Requires-Dist: pytest-bdd>=8.0; extra == 'test'
32
+ Requires-Dist: pytest>=8.0; extra == 'test'
33
+ Description-Content-Type: text/markdown
34
+
35
+ # talika
36
+
37
+ `talika` adds small, dataclass-style schemas to BDD data tables. It parses the
38
+ raw list-of-lists supplied by tools such as `pytest-bdd`, validates the table
39
+ shape, converts cells with project-defined parsers, and returns typed schema
40
+ records.
41
+
42
+ It does not prescribe a table DSL or perform business actions. Projects define
43
+ their own readable table vocabulary while `talika` handles the repeatable
44
+ parts: shape validation, conversion, source-aware diagnostics, and optional
45
+ static checks for `.feature` files.
46
+
47
+ ## Links
48
+
49
+ - Documentation: <https://talikadev.github.io/talika/>
50
+ - Source: <https://github.com/talikadev/talika>
51
+ - Issues: <https://github.com/talikadev/talika/issues>
52
+ - PyPI: <https://pypi.org/project/talika/>
53
+
54
+ ## Installation
55
+
56
+ `talika` supports Python 3.10 and newer. The core package has no runtime
57
+ dependencies:
58
+
59
+ ```bash
60
+ pip install talika
61
+ ```
62
+
63
+ Install optional extras only for integrations you use:
64
+
65
+ ```bash
66
+ pip install "talika[cli]" # static Gherkin feature-file checks
67
+ pip install "talika[pydantic]" # Pydantic output models
68
+ pip install "talika[test]" # test/integration dependencies
69
+ ```
70
+
71
+ The command-line tool is available as both `talika` and `python -m talika`.
72
+
73
+ ## Quick Start
74
+
75
+ ```python
76
+ from talika import RowTable, field
77
+
78
+
79
+ def parse_bool(value, context):
80
+ return value.lower() == "true"
81
+
82
+
83
+ class UserTable(RowTable):
84
+ name = field("name", required=True)
85
+ role = field("role", required=True)
86
+ active = field("active", parser=parse_bool, default=True)
87
+
88
+
89
+ users = UserTable.parse(
90
+ [
91
+ ["name", "role", "active"],
92
+ ["Alice", "admin", "true"],
93
+ ]
94
+ )
95
+
96
+ assert users[0].name == "Alice"
97
+ assert users[0].active is True
98
+ ```
99
+
100
+ ## Table Shapes
101
+
102
+ Row-oriented tables use the first row as labels and every following row as one
103
+ record:
104
+
105
+ ```python
106
+ from talika import RowTable, field
107
+
108
+
109
+ class ProductTable(RowTable):
110
+ sku = field("sku", required=True)
111
+ name = field("name", required=True)
112
+ ```
113
+
114
+ Column-oriented tables use the first column as labels and every following
115
+ column as one record:
116
+
117
+ ```python
118
+ from talika import ColumnTable, field, id_field
119
+
120
+
121
+ class ContentTable(ColumnTable):
122
+ id = id_field("IDs")
123
+ content_type = field("Type*", required=True)
124
+ headline = field("Headline*", required=True)
125
+ category = field("Category")
126
+
127
+
128
+ items = ContentTable.parse(
129
+ [
130
+ ["IDs", "1", "2"],
131
+ ["Type*", "Article", "Poll"],
132
+ ["Headline*", "Hello", "QA Poll"],
133
+ ]
134
+ )
135
+
136
+ assert items[0].id == "1"
137
+ assert items[1].content_type == "Poll"
138
+ assert items[0].category is None
139
+ ```
140
+
141
+ Use `parse_records()` when you specifically want schema instances for static
142
+ typing or when a schema has an `output_model` but a test needs the intermediate
143
+ validated record:
144
+
145
+ ```python
146
+ records: list[ContentTable] = ContentTable.parse_records(datatable)
147
+ ```
148
+
149
+ Functional helpers are also available:
150
+
151
+ ```python
152
+ from talika import parse_table, parse_table_records
153
+
154
+ items = parse_table(ContentTable, datatable)
155
+ records = parse_table_records(ContentTable, datatable)
156
+ ```
157
+
158
+ ## Conversion
159
+
160
+ Common field conversion does not require custom functions:
161
+
162
+ ```python
163
+ from talika import RowTable, boolean, compose, decimal, each, field, split, string
164
+
165
+
166
+ class ProductTable(RowTable):
167
+ price = field("price", parser=decimal())
168
+ active = field("active", parser=boolean())
169
+ tags = field("tags", parser=compose(split(","), each(string(strip=True))))
170
+ ```
171
+
172
+ The package provides `string`, `integer`, `floating`, `decimal`, `boolean`,
173
+ `choice`, `split`, `map_value`, `optional`, `compose`, and `each`.
174
+
175
+ Supported annotations infer a parser when the field has no explicit parser:
176
+
177
+ ```python
178
+ class UserTable(RowTable):
179
+ name: str = field("name")
180
+ age: int | None = field("age")
181
+ active: bool = field("active")
182
+ ```
183
+
184
+ Inference supports `str`, `int`, `float`, `bool`, `Decimal`, enums, string
185
+ `Literal` values, and simple optionals. Collection annotations such as
186
+ `list[str]` do not imply a cell syntax; use an explicit parser such as
187
+ `split(",")` when one cell should become several values.
188
+
189
+ ## Defaults, Aliases, And Policies
190
+
191
+ Missing optional fields can use static defaults or context-aware factories:
192
+
193
+ ```python
194
+ headline = field(
195
+ "Headline",
196
+ default_factory=lambda context: (
197
+ context.user_data["generator"].headline(context.item_id)
198
+ ),
199
+ )
200
+ ```
201
+
202
+ Aliases support intentional wording changes:
203
+
204
+ ```python
205
+ headline = field("Headline", aliases=("Title", "Name"))
206
+ ```
207
+
208
+ Unknown fields currently accept only the default `unknown_fields = "forbid"`
209
+ policy. Discriminated schemas also support `inapplicable_fields = "forbid"`
210
+ and `inapplicable_fields = "preserve"` for variant-specific values.
211
+
212
+ ## Variants
213
+
214
+ Variants let one BDD table contain several related record shapes. The base
215
+ schema declares shared fields and a discriminator used to select the applicable
216
+ fields and behavior.
217
+
218
+ ```python
219
+ from talika import ColumnTable, TableFields, discriminator, field, id_field, split
220
+
221
+
222
+ class ArticleFields(TableFields):
223
+ body = field("Body*", required=True)
224
+
225
+
226
+ class PollFields(TableFields):
227
+ options: list[str] = field("Options*", required=True, parser=split(","))
228
+
229
+
230
+ class ContentTable(ColumnTable):
231
+ id = id_field("IDs")
232
+ content_type = discriminator(
233
+ "Type*",
234
+ variants={
235
+ "Article": ArticleFields,
236
+ "Poll": PollFields,
237
+ },
238
+ )
239
+ headline = field("Headline*", required=True)
240
+ ```
241
+
242
+ The explicit decorator form is also available with `discriminator_field()` and
243
+ `@ContentTable.variant(...)`.
244
+
245
+ ## Custom Cell Syntax
246
+
247
+ `CellDSL` groups exact tokens and full-match regular-expression rules into a
248
+ reusable field parser. The package owns dispatch; your project owns the
249
+ meaning.
250
+
251
+ ```python
252
+ from talika import CellDSL, ColumnTable, field, id_field
253
+
254
+ content_cells = CellDSL()
255
+
256
+
257
+ @content_cells.token("random")
258
+ def random_value(context):
259
+ return context.user_data["generator"].random_for(context.field_name)
260
+
261
+
262
+ @content_cells.pattern(r"(?P<count>\d+):word")
263
+ def generated_words(match, context):
264
+ count = int(match["count"])
265
+ return context.user_data["generator"].words(count)
266
+
267
+
268
+ class ContentTable(ColumnTable):
269
+ id = id_field("IDs")
270
+ headline = field("Headline*", required=True, parser=content_cells)
271
+ ```
272
+
273
+ Exact tokens run before patterns. Patterns are tried in registration order and
274
+ must match the whole cell. Values that match no rule pass through unchanged.
275
+ Rules may be scoped by schema attribute name, and several DSLs may be composed
276
+ with `compose_cell_dsls(...)`.
277
+
278
+ ## Validation And Diagnostics
279
+
280
+ Override `validate_record()` to check rules involving one parsed record:
281
+
282
+ ```python
283
+ class ContentTable(ColumnTable):
284
+ id = id_field("IDs")
285
+ content_type = field("Type*", required=True)
286
+ headline = field("Headline*", required=True)
287
+
288
+ def validate_record(self, context):
289
+ if self.content_type == "Poll" and not self.headline.endswith("?"):
290
+ raise ValueError("Poll headlines must end with a question mark")
291
+ ```
292
+
293
+ Use `validate_records()` for relationships involving several records. Failures
294
+ become `TableError` instances with stable error codes and source coordinates.
295
+ In collect mode, independent diagnostics are grouped into `TableErrors`:
296
+
297
+ ```python
298
+ from talika import TableErrors
299
+
300
+ try:
301
+ ContentTable.parse(datatable, error_mode="collect")
302
+ except TableErrors as errors:
303
+ for error in errors:
304
+ print(error.code, error.row, error.column, error.message)
305
+ ```
306
+
307
+ Schema records expose immutable source metadata:
308
+
309
+ ```python
310
+ record.table_source.row
311
+ record.table_source.column
312
+ record.table_source.item_id
313
+ record.source_for("headline")
314
+ ```
315
+
316
+ ## Output Models
317
+
318
+ Set `output_model` to return project objects after schema and table validation:
319
+
320
+ ```python
321
+ from dataclasses import dataclass
322
+
323
+
324
+ @dataclass(frozen=True)
325
+ class User:
326
+ name: str
327
+ age: int
328
+
329
+
330
+ class UserTable(RowTable):
331
+ output_model = User
332
+
333
+ name = field("name")
334
+ age: int = field("age")
335
+ ```
336
+
337
+ Dataclasses and other keyword-constructed classes need no integration
338
+ dependency. Pydantic v2 works through the optional `talika[pydantic]` extra.
339
+ Override `build_output(record, context)` when construction needs a custom
340
+ signature, selected fields, source metadata, or project services.
341
+
342
+ ## Table Transformations
343
+
344
+ `ColumnGroupExpander` handles a common compact table convention where one
345
+ source column describes one item or a group of items:
346
+
347
+ ```python
348
+ from talika import ColumnGroupExpander, NumericRange, PrefixRepeat
349
+
350
+
351
+ class ContentTable(ColumnTable):
352
+ table_transformer = ColumnGroupExpander(
353
+ key_row="IDs",
354
+ range_rule=NumericRange(separator=".."),
355
+ repeat_rule=PrefixRepeat(separator=":"),
356
+ )
357
+
358
+ id = id_field("IDs")
359
+ content_type = field("Type*", required=True)
360
+ ```
361
+
362
+ Projects can also implement compatible `RangeRule` and `RepeatRule` objects or
363
+ override `transform_table()` for table syntax that does not fit the reusable
364
+ grouped-column shape.
365
+
366
+ ## pytest Integration
367
+
368
+ Installing the package registers a `talika` fixture:
369
+
370
+ ```python
371
+ def content_exists(datatable, talika, faker):
372
+ return talika.parse(
373
+ datatable,
374
+ schema=ContentTable,
375
+ context={"faker": faker},
376
+ )
377
+ ```
378
+
379
+ The fixture also exposes `parse_records()` for type-checker-friendly schema
380
+ records.
381
+
382
+ ## Static Feature Checking
383
+
384
+ Install the optional CLI extra to validate feature tables without executing
385
+ pytest scenarios:
386
+
387
+ ```powershell
388
+ pip install "talika[cli]"
389
+ talika check features/content.feature `
390
+ --schema tests/support/content_schema.py:ContentTable `
391
+ --step "the following content exists:"
392
+ ```
393
+
394
+ Machine-readable diagnostics are available for CI and editor integrations:
395
+
396
+ ```powershell
397
+ talika check features/content.feature `
398
+ --schema tests/support/content_schema.py:ContentTable `
399
+ --step "the following content exists:" `
400
+ --format json
401
+ ```
402
+
403
+ Use `describe` to inspect a schema without parsing a feature file:
404
+
405
+ ```powershell
406
+ talika describe tests/support/content_schema.py:ContentTable
407
+ talika describe tests/support/content_schema.py:ContentTable --format json
408
+ ```
409
+
410
+ The checker uses the official Gherkin parser and reports exact feature-file
411
+ coordinates. Importable `module:Schema` references are also supported. Use
412
+ `--context-factory module:function` for deterministic parser dependencies.
413
+ Scenario-outline substitutions are not expanded.
414
+
415
+ ## Development
416
+
417
+ ```powershell
418
+ uv sync --all-extras --dev
419
+ uv run pytest -p no:cacheprovider
420
+ uv run ruff check .
421
+ uv run ruff format --check .
422
+ uv run mypy src tests/typing/public_api.py
423
+ uv build
424
+ ```
425
+
426
+ The documentation site is built with Zensical:
427
+
428
+ ```powershell
429
+ uv run --group docs zensical build --strict
430
+ ```
431
+
432
+ GitHub Pages is configured for <https://talikadev.github.io/talika/> through
433
+ the `Docs` workflow in `.github/workflows/docs.yml`.