CellDSL Tokens⚓︎
CellDSL lets a project give special meaning to authored cell text.
Talika does not ship built-in meanings for words like random, today,
current user, or default. Those words are project vocabulary. A CellDSL
lets you define that vocabulary once and attach it to the fields where it is
allowed.
Feature: CMS generated content
Scenario: Use project tokens in content setup
Given the content items:
| IDs | A-1 |
| Headline | random |
| Status | random |
Then only the headline token is expanded
In this table, random should generate a headline, but it should not generate
a status. The same visible cell text can mean something in one field and stay
literal in another field.
A token is exact project vocabulary
Use token rules for short, exact cell values such as random, today,
none, current user, or published. If the value has parameters, a
pattern rule is usually a better fit.
Create a Token DSL⚓︎
Start by creating one CellDSL instance and registering exact tokens on it.
from talika import CellDSL, ColumnTable, RowTable, field, id_field
content_cells = CellDSL()
@content_cells.token("random", fields=("headline",))
def random_headline(context):
generator = context.user_data["generator"]
return generator(context.item_id)
The decorated function receives a CellContext. It can read parse-time project
data from context.user_data, source identity from context.row and
context.column, and record identity from context.item_id when the table has
an ID.
The token value is exact. This rule matches the cell text random. It does not
match Random, random, random headline, or any other spelling.
Tokens do not normalize text
If your project wants case-insensitive or more flexible syntax, normalize the table before parsing or use a pattern/predicate rule in the later DSL topics. Exact tokens stay exact.
Attach the DSL to Fields⚓︎
A CellDSL is a field parser. Attach it with field(..., parser=...).
class ContentTable(ColumnTable):
id = id_field("IDs")
headline = field("Headline", required=True, parser=content_cells)
status = field("Status", parser=content_cells)
The same parser is attached to headline and status, but the token itself is
scoped to headline.
content_table = [
["IDs", "A-1"],
["Headline", "random"],
["Status", "random"],
]
record = ContentTable.parse(
content_table,
context={
"generator": lambda item_id: f"Generated headline for {item_id}",
},
)[0]
>> record
ContentTable(id='A-1', headline='Generated headline for A-1', status='random')
>> record.headline
'Generated headline for A-1'
>> record.status
'random'
The headline token matched, so the handler generated a value. The status cell
also contains random, but the token is not scoped to status, so the value
passes through unchanged.
A DSL may be shared by several fields
The DSL can be attached to multiple fields. Field scopes decide which rules apply to which schema attribute.
Use Cell Context⚓︎
Token handlers receive CellContext, not only the raw value. For exact tokens,
the value is already known because it is the token being handled.
context_cells = CellDSL()
@context_cells.token("where")
def where(context):
return {
"field": context.field_name,
"label": context.field_label,
"row": context.row,
"column": context.column,
"item_id": context.item_id,
"source": context.source_value,
}
class ContextTable(ColumnTable):
id = id_field("IDs")
headline = field("Headline", parser=context_cells)
>> ContextTable.parse([["IDs", "A-1"], ["Headline", "where"]])[0].headline
{'field': 'headline', 'label': 'Headline', 'row': 2, 'column': 2, 'item_id': 'A-1', 'source': 'where'}
The context gives token handlers enough information to generate values that belong to the current record and field. This is why a CMS headline token can use the current item ID, or a date token can read a clock supplied through parse context.
Pass dependencies through parse context
Put generators, clocks, fixtures, configuration, and policy values in
parse(..., context={...}). Do not close over test-local mutable state
when the context can make the dependency explicit.
Scope Tokens by Python Field Name⚓︎
The fields= argument uses schema attribute names, not table labels.
The field scope is "headline", not "Headline".
This distinction matters because table labels are user-facing vocabulary. They may contain spaces, punctuation, aliases, or old names. Field scopes belong to Python schema code and use the stable attribute name.
Use field names, not labels
A field declared as headline = field("Headline*") is scoped as
"headline". Passing "Headline*" to fields= will not match that field.
Scoped Tokens Beat Global Tokens⚓︎
You can register a global token and a field-scoped token with the same exact value.
scoped_cells = CellDSL()
@scoped_cells.token("random")
def global_random(context):
return "global value"
@scoped_cells.token("random", fields=("headline",))
def scoped_random(context):
return "headline value"
class ScopedTable(RowTable):
headline = field("headline", parser=scoped_cells)
category = field("category", parser=scoped_cells)
>> ScopedTable.parse([["headline", "category"], ["random", "random"]])[0]
ScopedTable(headline='headline value', category='global value')
For headline, the scoped token wins. For category, no scoped token applies,
so the global token handles the value.
This is useful when a broad project token has a sensible default meaning, but one field needs a more specific meaning.
Token precedence is narrow
This precedence rule is for exact tokens with the same value. Pattern, predicate, fallback, and composed DSL precedence are covered separately.
Unmatched Values Pass Through⚓︎
If no token matches, a CellDSL returns the original cell value.
literal_cells = CellDSL()
class LiteralTable(RowTable):
value = field("value", parser=literal_cells)
This lets one parser accept both special project tokens and ordinary literal
values. A headline field can support random while still accepting a normal
authored headline.
Keep literals valid
A token DSL does not force every value to be a token. Use it when a field should accept a small set of special values plus ordinary table text.
Registration Is Validated⚓︎
An exact token cannot be empty.
The same token and same scope cannot be registered twice.
cells = CellDSL()
@cells.token("random")
def first_random(context):
return "first"
@cells.token("random")
def second_random(context):
return "second"
Duplicate detection is per scope. A global random token and a
fields=("headline",) random token can coexist because they do not describe
the same rule.
Registration errors happen early
These errors happen when the DSL is defined, not when a feature table is parsed. That helps catch ambiguous project vocabulary during import.
Token Handler Errors Keep Source Context⚓︎
If a token handler raises an exception, Talika wraps it as a field parser failure and keeps the source location of the cell that triggered the token.
broken_cells = CellDSL()
@broken_cells.token("broken")
def broken(context):
raise RuntimeError("generator unavailable")
class BrokenTable(ColumnTable):
id = id_field("IDs")
headline = field("Headline", parser=broken_cells)
Field parser failed: generator unavailable (code=parser_failed, schema=BrokenTable, field='Headline', row=2, column=2, item_id='A-1', value='broken'). Hint: Check the cell value or adjust the field parser for this syntax.
The failure points to Headline row 2, column 2, the authored cell containing
broken. That is the right place for the table author or test maintainer to
start looking.
Keep token handlers deterministic
A token handler is still a parser. It should return a value for the current cell or raise a clear exception. Avoid hidden side effects that make the same table parse differently across runs.
Choose Tokens for Stable Vocabulary⚓︎
Use exact tokens for values that should read like named project vocabulary:
randomtodaycurrent usernonepublisheddefault
Avoid exact tokens for values that contain parameters, counts, IDs, or ranges. Those belong in pattern, predicate, or transformation rules where the variable part can be parsed deliberately.
The best token names are short, predictable, and documented by the schema that
uses them. A reader should be able to see random in a field and know that the
project has assigned that word a specific meaning for that field.
Prefer clear token names
A good token is small, predictable table language. It removes repeated setup code and keeps authored examples readable.