Skip to content

Commit

Permalink
feat(types): add more grammar types
Browse files Browse the repository at this point in the history
  • Loading branch information
syu-w committed Mar 28, 2024
1 parent 3d2ef93 commit 7145072
Show file tree
Hide file tree
Showing 3 changed files with 208 additions and 5 deletions.
98 changes: 98 additions & 0 deletions craft_grammar/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,78 @@ def _grammar_append(cls, entry: List, item: Any) -> None:
# Public types for grammar-enabled attributes


class GrammarBool(_GrammarBase):
"""Grammar-enabled bool field."""

__root__: Union[bool, _GrammarType]

@classmethod
@overrides
def validate(cls, entry):
# GrammarInt entry can be a list if it contains clauses
if isinstance(entry, list):
new_entry = []
for item in entry:
if _is_grammar_clause(item):
cls._grammar_append(new_entry, item)
else:
raise TypeError(f"value must be a list of bool: {entry!r}")
return new_entry

if isinstance(entry, bool):
return entry

raise TypeError(f"value must be a bool: {entry!r}")


class GrammarInt(_GrammarBase):
"""Grammar-enabled integer field."""

__root__: Union[int, _GrammarType]

@classmethod
@overrides
def validate(cls, entry):
# GrammarInt entry can be a list if it contains clauses
if isinstance(entry, list):
new_entry = []
for item in entry:
if _is_grammar_clause(item):
cls._grammar_append(new_entry, item)
else:
raise TypeError(f"value must be a list of integer: {entry!r}")
return new_entry

if isinstance(entry, int):
return entry

raise TypeError(f"value must be a integer: {entry!r}")


class GrammarFloat(_GrammarBase):
"""Grammar-enabled float field."""

__root__: Union[float, _GrammarType]

@classmethod
@overrides
def validate(cls, entry):
# GrammarFloat entry can be a list if it contains clauses
if isinstance(entry, list):
new_entry = []
for item in entry:
if _is_grammar_clause(item):
cls._grammar_append(new_entry, item)
else:
raise TypeError(f"value must be a list of float: {entry!r}")
return new_entry

if isinstance(entry, (int, float)):
return float(entry)

raise TypeError(f"value must be a float: {entry!r}")


class GrammarStr(_GrammarBase):
"""Grammar-enabled string field."""

Expand Down Expand Up @@ -128,6 +200,32 @@ def validate(cls, entry):
raise TypeError(f"value must be a list of single-entry dictionaries: {entry!r}")


class GrammarDict(_GrammarBase):
"""Grammar-enabled dictionary field."""

__root__: Union[List[Dict[str, Any]], _GrammarType]

@classmethod
@overrides
def validate(cls, entry):
# GrammarSingleEntryDictList will always be a list
if isinstance(entry, list):
new_entry = []
for item in entry:
if _is_grammar_clause(item):
cls._grammar_append(new_entry, item)
elif isinstance(item, dict):
new_entry.append(item)
else:
raise TypeError(f"value must be a list of dictionaries: {entry!r}")
return new_entry

if isinstance(entry, dict):
return entry

raise TypeError(f"value must be a dictionary: {entry!r}")


def _ensure_selector_valid(selector: str, *, clause: str) -> None:
"""Verify selector syntax."""
# spaces are not allowed in selector
Expand Down
1 change: 0 additions & 1 deletion tests/unit/test_compound.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,6 @@

@pytest.mark.parametrize("scenario", scenarios)
def test_compound_statement(scenario):

processor = GrammarProcessor(
arch=scenario["arch"],
target_arch="armhf",
Expand Down
114 changes: 110 additions & 4 deletions tests/unit/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,27 @@
import pytest
import yaml

from craft_grammar.models import GrammarSingleEntryDictList, GrammarStr, GrammarStrList
from craft_grammar.models import (
GrammarSingleEntryDictList,
GrammarStr,
GrammarStrList,
GrammarBool,
GrammarInt,
GrammarFloat,
GrammarDict,
)


class ValidationTest(pydantic.BaseModel):
"""A test model containing all types of grammar-aware types."""

control: str
grammar_bool: GrammarBool
grammar_int: GrammarInt
grammar_float: GrammarFloat
grammar_str: GrammarStr
grammar_strlist: GrammarStrList
grammar_dict: GrammarDict
grammar_single_entry_dictlist: GrammarSingleEntryDictList


Expand All @@ -38,11 +50,17 @@ def test_validate_grammar_trivial():
textwrap.dedent(
"""
control: a string
grammar_bool: true
grammar_int: 42
grammar_float: 3.14
grammar_str: another string
grammar_strlist:
- a
- string
- list
grammar_dict:
key: value
other_key: other_value
grammar_single_entry_dictlist:
- key: value
- other_key: other_value
Expand All @@ -52,8 +70,12 @@ def test_validate_grammar_trivial():

v = ValidationTest(**data)
assert v.control == "a string"
assert v.grammar_bool == True # noqa: E712
assert v.grammar_int == 42
assert v.grammar_float == 3.14
assert v.grammar_str == "another string"
assert v.grammar_strlist == ["a", "string", "list"]
assert v.grammar_dict == {"key": "value", "other_key": "other_value"}
assert v.grammar_single_entry_dictlist == [
{"key": "value"},
{"other_key": "other_value"},
Expand All @@ -65,14 +87,28 @@ def test_validate_grammar_simple():
textwrap.dedent(
"""
control: a string
grammar_bool:
- on amd64: true
- else: false
grammar_int:
- on amd64: 42
- else: 23
grammar_float:
- on amd64: 3.14
- else: 2.71
grammar_str:
- on amd64: another string
- else: something different
grammar_strlist:
- to amd64,arm64:
- a
- string
- list
- a
- string
- list
- else fail
grammar_dict:
- on amd64:
key: value
other_key: other_value
- else fail
grammar_single_entry_dictlist:
- on arch:
Expand All @@ -85,6 +121,18 @@ def test_validate_grammar_simple():

v = ValidationTest(**data)
assert v.control == "a string"
assert v.grammar_bool == [
{"*on amd64": True},
{"*else": False},
]
assert v.grammar_int == [
{"*on amd64": 42},
{"*else": 23},
]
assert v.grammar_float == [
{"*on amd64": 3.14},
{"*else": 2.71},
]
assert v.grammar_str == [
{"*on amd64": "another string"},
{"*else": "something different"},
Expand All @@ -93,6 +141,10 @@ def test_validate_grammar_simple():
{"*to amd64,arm64": ["a", "string", "list"]},
"*else fail",
]
assert v.grammar_dict == [
{"*on amd64": {"key": "value", "other_key": "other_value"}},
"*else fail",
]
assert v.grammar_single_entry_dictlist == [
{"*on arch": [{"key": "value"}, {"other_key": "other_value"}]},
"*else fail",
Expand All @@ -104,6 +156,21 @@ def test_validate_grammar_recursive():
textwrap.dedent(
"""
control: a string
grammar_bool:
- on amd64: true
- else:
- to arm64: false
- else fail
grammar_int:
- on amd64: 42
- else:
- to arm64: 23
- else fail
grammar_float:
- on amd64: 3.14
- else:
- to arm64: 2.71
- else fail
grammar_str:
- on amd64: another string
- else:
Expand All @@ -127,6 +194,16 @@ def test_validate_grammar_recursive():
- else:
- other
- stuff
grammar_dict:
- on amd64,arm64:
key: value
other_key: other_value
- else:
- on other_arch:
- to yet_another_arch:
yet_another_key: yet_another_value
- else fail
- else fail
grammar_single_entry_dictlist:
- on arch,other_arch:
- on other_arch:
Expand All @@ -143,6 +220,18 @@ def test_validate_grammar_recursive():

v = ValidationTest(**data)
assert v.control == "a string"
assert v.grammar_bool == [
{"*on amd64": True},
{"*else": [{"*to arm64": False}, "*else fail"]},
]
assert v.grammar_int == [
{"*on amd64": 42},
{"*else": [{"*to arm64": 23}, "*else fail"]},
]
assert v.grammar_float == [
{"*on amd64": 3.14},
{"*else": [{"*to arm64": 2.71}, "*else fail"]},
]
assert v.grammar_str == [
{"*on amd64": "another string"},
{
Expand Down Expand Up @@ -173,6 +262,23 @@ def test_validate_grammar_recursive():
},
{"*else": ["other", "stuff"]},
]
assert v.grammar_dict == [
{"*on amd64,arm64": {"key": "value", "other_key": "other_value"}},
{
"*else": [
{
"*on other_arch": [
{
"*to yet_another_arch": {"yet_another_key": "yet_another_value"}
},
"*else fail",
]
},
"*else fail",
]
},
]

assert v.grammar_single_entry_dictlist == [
{
"*on arch,other_arch": [
Expand Down

0 comments on commit 7145072

Please sign in to comment.