Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Options api rework #671

Merged
merged 12 commits into from
Jan 22, 2025
33 changes: 16 additions & 17 deletions codegen/generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@

from jinja2 import Environment, FileSystemLoader

from keyword_generation.handlers.shared_field import SharedFieldHandler
from keyword_generation.handlers.handler_base import KeywordHandler


SKIPPED_KEYWORDS = set(
[
# defined manually because of the variable length text card
Expand Down Expand Up @@ -288,6 +292,7 @@ def handle_override_field(kwd_data, settings):
if "new-name" in setting:
field["name"] = setting["new-name"]


def handle_rename_property(kwd_data, settings):
for setting in settings:
index = setting["index"]
Expand All @@ -299,19 +304,6 @@ def handle_rename_property(kwd_data, settings):
field["property_name"] = property_name


def handle_shared_field(kwd_data, settings):
for setting in settings:
fields = []
for card in kwd_data["cards"]:
for field in card["fields"]:
if field["name"] == setting["name"]:
fields.append(field)
assert len(fields) > 1
fields[0]["card_indices"] = setting["cards"]
for field in fields[1:]:
field["redundant"] = True


def handle_override_subkeyword(kwd_data, settings) -> None:
kwd_data["subkeyword"] = settings

Expand Down Expand Up @@ -351,7 +343,7 @@ def expand(card):
"rename-property": handle_rename_property,
"skip-card": handle_skipped_cards,
"duplicate-card-group": handle_duplicate_card_group,
"shared-field": handle_shared_field,
"shared-field": SharedFieldHandler(),
"override-subkeyword": handle_override_subkeyword,
}
)
Expand Down Expand Up @@ -387,7 +379,6 @@ def add_option_indices(kwd_data):
card["index"] = index
index += 1


def add_indices(kwd_data):
# handlers might point to cards by a specific index.
for index, card in enumerate(kwd_data["cards"]):
Expand Down Expand Up @@ -435,6 +426,9 @@ def after_handle(kwd_data):
do_insertions(kwd_data)
delete_marked_indices(kwd_data)
add_option_indices(kwd_data)
for handler_name, handler in HANDLERS.items():
if isinstance(handler, KeywordHandler):
handler.post_process(kwd_data)


def before_handle(kwd_data):
Expand All @@ -446,11 +440,16 @@ def handle_keyword_data(kwd_data, settings):
before_handle(kwd_data)
# we have to iterate in the order of the handlers because right now the order still matters
# right now this is only true for reorder_card
for handler_name, handler_func in HANDLERS.items():
for handler_name, handler in HANDLERS.items():
handler_settings = settings.get(handler_name)
if handler_settings == None:
continue
handler_func(kwd_data, handler_settings)
# handler can be a KeywordHandler object or a function pointer
# TODO - change all handlers to objects
if isinstance(handler, KeywordHandler):
handler.handle(kwd_data, handler_settings)
else:
handler(kwd_data, handler_settings)
after_handle(kwd_data)


Expand Down
Empty file.
15 changes: 15 additions & 0 deletions codegen/keyword_generation/handlers/handler_base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import abc
import typing

class KeywordHandler(metaclass=abc.ABCMeta):
"""Abstract base class for keyword handlers."""

@abc.abstractmethod
def handle(self, kwd_data: typing.Dict[str, typing.Any], settings: typing.Dict[str, typing.Any]) -> None:
"""Transform `kwd_data` based on `settings`."""
raise NotImplementedError

@abc.abstractmethod
def post_process(self, kwd_data: typing.Dict[str, typing.Any]) -> None:
"""Run after all handlers have run."""
raise NotImplementedError
67 changes: 67 additions & 0 deletions codegen/keyword_generation/handlers/shared_field.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import typing

import keyword_generation.handlers.handler_base

def do_negative_shared_fields(kwd_data: typing.Dict):
negative_shared_fields = kwd_data.get("negative_shared_fields", [])
num_cards = len(kwd_data["cards"])
options = kwd_data.get("options", [])
option_cards = []
for options in kwd_data.get("options", []):
option_cards.extend(options["cards"])
for setting in negative_shared_fields:
indices = [-i for i in setting["cards"]]
fields = []
for index in indices:
if index >= num_cards:
for options in kwd_data.get("options", []):
for card in options["cards"]:
if card["index"] == index:
for field in card["fields"]:
if field["name"] == setting["name"]:
fields.append(field)
else:
assert False, "TODO - support negative indices for shared fields for non-options"
assert len(fields) > 1
if not setting["applied_card_indices"]:
fields[0]["card_indices"] = indices
for field in fields[1:]:
field["redundant"] = True


def handle_shared_field(kwd_data, settings):
# positive card indices are applied in handler
# negative card indices are marked and handled after transformations (after_handle)
for setting in settings:
setting["applied_card_indices"] = False
cards = setting["cards"]
num_positive = len([c for c in cards if c > 0])

# either or - we cannot support some positive some negative in the same setting now
assert num_positive == 0 or num_positive == len(cards)
if num_positive > 0:
fields = []
for card in kwd_data["cards"]:
for field in card["fields"]:
if field["name"] == setting["name"]:
fields.append(field)
assert len(fields) > 1
fields[0]["card_indices"] = cards
setting["applied_card_indices"] = True
for field in fields[1:]:
field["redundant"] = True
else:
if "negative_shared_fields" not in kwd_data:
kwd_data["negative_shared_fields"] = []
kwd_data["negative_shared_fields"].append(setting)


class SharedFieldHandler(keyword_generation.handlers.handler_base.KeywordHandler):

def handle(self, kwd_data: typing.Dict[str, typing.Any], settings: typing.Dict[str, typing.Any]) -> None:
"""Transform `kwd_data` based on `settings`."""
return handle_shared_field(kwd_data, settings)

def post_process(self, kwd_data: typing.Dict[str, typing.Any]) -> None:
"""Run after all handlers have run."""
return do_negative_shared_fields(kwd_data)
29 changes: 25 additions & 4 deletions codegen/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,8 @@
"generation-options": {
"add-option": [
{
"card-order": 1,
"title-order": 0,
"card-order": -1,
"title-order": 1,
"cards": [
{
"source": "kwd-data",
Expand All @@ -122,6 +122,28 @@
}
],
"option-name": "ID"
},
{
"card-order": -1,
"title-order": 1,
"cards": [
{
"source": "kwd-data",
"keyword-name": "CONSTRAINED_BEAM_IN_SOLID",
"card-index": 0
}
],
"option-name": "TITLE"
}
],
"shared-field": [
{
"name": "COUPID",
"cards": [-2, -3]
},
{
"name": "TITLE",
"cards": [-2, -3]
}
],
"skip-card": 0,
Expand All @@ -132,8 +154,7 @@
"new-name": "ncoup"
}
]
},
"comment": "TODO - the option name is either ID or TITLE, but there is no way in pydyna to have a union option"
}
},
"DEFINE_TRANSFORMATION": {
"generation-options": {
Expand Down
9 changes: 9 additions & 0 deletions codegen/templates/keyword/option_card_properties.j2
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
{% set card_loop = loop %}
{% for field in card.fields %}
{% if field.used %}
{% if not field.redundant %}
@property
def {{field.property_name}}(self) -> {%- if field.default is none %} typing.Optional[
{%- else %} {% endif %}{{field.property_type}}{%- if field.default is none %}]{%- endif %}:
Expand All @@ -17,9 +18,17 @@
if value not in [{{ field.options|join(', ')}}]:
raise Exception("""{{field.property_name}} must be one of {{openbrace}}{{ field.options|join(',')}}{{closebrace}}""")
{% endif %}
{% if field.card_indices %}
{# This is COMPLETELY wrong but it works for CONSTRAINED_BEAM_IN_SOLID. REVISIT! #}
{% for card_index in field.card_indices %}
self._cards[{{card_index}}].cards[{{card_loop.index-1}}].set_value("{{field.name}}", value)
{% endfor %}{# card_index in field.card_indices #}
{% else %}
self._cards[{{card.index}}].cards[{{card_loop.index-1}}].set_value("{{field.name}}", value)
{% endif %}{# card.indices #}

{% endif %}{# not field.readonly #}
{% endif %}{# not field.redundant #}
{% endif %}{# field.used #}
{% endfor %}{# field in card.fields #}
{% endfor %}{# card in option.cards #}
1 change: 1 addition & 0 deletions doc/changelog/671.documentation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fix: Options api rework
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ class ConstrainedBeamInSolid(KeywordBase):
keyword = "CONSTRAINED"
subkeyword = "BEAM_IN_SOLID"
option_specs = [
OptionSpec("ID", 1, 0),
OptionSpec("ID", -1, 1),
OptionSpec("TITLE", -1, 1),
]

def __init__(self, **kwargs):
Expand Down Expand Up @@ -183,6 +184,30 @@ def __init__(self, **kwargs):
],
**kwargs
),
OptionCardSet(
option_spec = ConstrainedBeamInSolid.option_specs[1],
cards = [
Card(
[
Field(
"coupid",
int,
0,
10,
kwargs.get("coupid")
),
Field(
"title",
str,
10,
70,
kwargs.get("title")
),
],
),
],
**kwargs
),
]

@property
Expand Down Expand Up @@ -325,6 +350,7 @@ def coupid(self) -> typing.Optional[int]:
@coupid.setter
def coupid(self, value: int) -> None:
self._cards[2].cards[0].set_value("coupid", value)
self._cards[3].cards[0].set_value("coupid", value)

@property
def title(self) -> typing.Optional[str]:
Expand All @@ -335,4 +361,5 @@ def title(self) -> typing.Optional[str]:
@title.setter
def title(self, value: str) -> None:
self._cards[2].cards[0].set_value("title", value)
self._cards[3].cards[0].set_value("title", value)

Loading
Loading