Skip to content

Commit

Permalink
Allow expander to return typed dictionaries
Browse files Browse the repository at this point in the history
This commit adds the ability for the expander to return a typed
dictionary based on variable definitions.

Subscripting of dictionaries will also return the value of the key, from
the expander now.
i.e. `'{dict["key"]}'` will return the value of the key.
  • Loading branch information
douglasjacobsen committed Dec 12, 2024
1 parent 7aeb0cc commit 87769df
Show file tree
Hide file tree
Showing 2 changed files with 35 additions and 15 deletions.
42 changes: 27 additions & 15 deletions lib/ramble/ramble/expander.py
Original file line number Diff line number Diff line change
Expand Up @@ -717,7 +717,7 @@ def eval_math(self, node):
elif isinstance(node, ast.Call):
return self._eval_function_call(node)
elif isinstance(node, ast.Subscript):
return self._eval_susbscript_op(node)
return self._eval_subscript_op(node)
else:
node_type = str(type(node))
raise MathEvaluationError(
Expand Down Expand Up @@ -906,24 +906,36 @@ def _eval_unary_ops(self, node):
except KeyError:
raise SyntaxError("Unsupported unary operator")

def _eval_susbscript_op(self, node):
def _eval_subscript_op(self, node):
"""Evaluate subscript operation in the ast"""
try:
operand = self.eval_math(node.value)
slice_node = node.slice
if not isinstance(operand, str) or not isinstance(slice_node, ast.Slice):
raise SyntaxError("Currently only string slicing is supported for subscript")

def _get_with_default(s_node, attr, default):
v_node = getattr(s_node, attr)
if v_node is None:
return default
return self.eval_math(v_node)

lower = _get_with_default(slice_node, "lower", 0)
upper = _get_with_default(slice_node, "upper", len(operand))
step = _get_with_default(slice_node, "step", 1)
return operand[slice(lower, upper, step)]

if isinstance(operand, str):
if isinstance(slice_node, ast.Slice):

def _get_with_default(s_node, attr, default):
v_node = getattr(s_node, attr)
if v_node is None:
return default
return self.eval_math(v_node)

lower = _get_with_default(slice_node, "lower", 0)
upper = _get_with_default(slice_node, "upper", len(operand))
step = _get_with_default(slice_node, "step", 1)
return operand[slice(lower, upper, step)]
elif operand in self._variables and isinstance(self._variables[operand], dict):
op_dict = self.expand_var_name(operand, typed=True)

if isinstance(slice_node, ast.Constant) or _safe_str_node_check(slice_node):
key = self.eval_math(slice_node)
return op_dict[key]

raise SyntaxError(
"Currently subscripts are only support "
"for string slicing, and key extraction from dictionaries"
)
except TypeError:
raise SyntaxError("Unsupported operand type in subscript operator")

Expand Down
8 changes: 8 additions & 0 deletions lib/ramble/ramble/renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,14 @@ def render_objects(self, render_group, exclude_where=None, ignore_used=True, fat
object_variables = {}
expander = ramble.expander.Expander(variables, None)

# Convert all dict types to base dicts
# This allows the expander to properly return typed dicts.
# Without this, all dicts are ruamel.CommentedMaps, and these
# cannot be evaled using ast.literal_eval
for var, val in variables.items():
if isinstance(val, dict):
variables[var] = dict(val)

# Expand all variables that generate lists
for name, unexpanded in variables.items():
value = expander.expand_lists(unexpanded)
Expand Down

0 comments on commit 87769df

Please sign in to comment.