Skip to content

Commit

Permalink
simplified selectors
Browse files Browse the repository at this point in the history
  • Loading branch information
sbromberger committed Feb 27, 2024
1 parent f3f193f commit 7640f9c
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 43 deletions.
90 changes: 59 additions & 31 deletions src/clippy/backends/expression.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,18 @@

""" Holds the expression building code. """

from __future__ import annotations
import json
from .serialization import ClippySerializable
from ..anydict import AnyDict

from typing import Any


class Expression(ClippySerializable):
def __init__(self, op, o1, o2):
def __init__(self, op: str | None, o1: Selector, o2: Any):
super().__init__()
# if op is None then this expression is just a native Selector held in o1.
self.op = op
self.o1 = o1
self.o2 = o2
Expand Down Expand Up @@ -120,49 +125,72 @@ def to_serial(self):
return {self.op: [o1, o2]}


class Field(Expression):
def __init__(self, name):
class Selector(Expression): # pylint: disable=W0223
def __init__(self, parent: Selector | None, name: str, docstr: str):
super().__init__(None, self, None) # op and o2 are None to represent this as a variable.
self.parent = parent
self.name = name
setattr(self, '__doc__', docstr)
self.fullname: str = self.name if self.parent is None else f"{self.parent.fullname}.{self.name}"
self.subselectors: set[Selector] = set()

def __hash__(self):
return hash(self.fullname)

def to_dict(self):
return {'var': self.name}
return {'var': self.fullname}

def hierarchy(self, acc: list[tuple[str, str]] | None = None):
if acc is None:
acc = []
acc.append((self.fullname, self.__doc__))
for subsel in self.subselectors:
subsel.hierarchy(acc)
return acc

def describe(self):
hier = self.hierarchy()
maxlen = max((len(sub_desc[0]) for sub_desc in hier))
return '\n'.join(f'{sub_desc[0]:<{maxlen+2}} {sub_desc[1]}' for sub_desc in hier)

def __str__(self):
return str(self.to_dict())
return repr(self.to_dict())

def __repr__(self):
return str(self.to_dict())
return repr(self.to_dict())

def to_json(self):
return json.dumps(self.to_dict())

def to_serial(self):
return {"var": self.name}
return {"var": self.fullname}

def _express(self, op, o):
return Expression(op, self, o)


class Selector(Expression):
def __init__(self, parent, name):
# not used at the moment but could be potentially used
# to get parent state information that can inform the
# field/expression creation
self.parent = parent
self._fld_name_sel_08 = name

def __str__(self):
return str(self.to_dict())

def __repr__(self):
return str(self.to_dict())

def to_dict(self):
return {"var": self._fld_name_sel_08}

def to_json(self):
return json.dumps(self.to_dict())

def __getattr__(self, key):
field = Field(f"{self._fld_name_sel_08}.{key}")
return field
def _add_subselector(self, name: str, docstr: str):
'''add a subselector to this selector'''
subsel = Selector(self, name, docstr)
self.__setattr__(name, subsel)
self.subselectors.add(subsel)

def _del_subselector(self, name: str):
delattr(self, name)
self.subselectors.remove(getattr(self, name))

def _clear_subselectors(self):
'''removes all subselectors'''
for subsel in self.subselectors:
delattr(self, subsel)
self.subselectors = []

def _import_from_dict(self, d: AnyDict, merge: bool = False):
'''Imports subselectors from a dictionary.
If `merge = True`, do not clear subselectors first.'''
# clear all children
if not merge:
self._clear_subselectors()
for name, subdict in d.items():
docstr = subdict.get('__doc__', '')
self._add_subselector(name, docstr)
getattr(self, name)._import_from_dict(subdict.get('subselectors', {}))
19 changes: 12 additions & 7 deletions src/clippy/backends/fs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
from ..serialization import ClippySerializable

from ... import config as topconfig
from ...constants import JSON_FLAG, CLASS_META_FILE, STATE_KEY
from ...error import ClippyConfigurationError, ClippyTypeError, ClippyValidationError
from ...constants import JSON_FLAG, CLASS_META_FILE, STATE_KEY, SELECTOR_KEY
from ...error import ClippyConfigurationError, ClippyTypeError, ClippyValidationError, ClippyInvalidSelectorError

PATH = sys.path[0]

Expand Down Expand Up @@ -49,7 +49,7 @@ def _create_class(name: str, path: str):
with open(metafile, 'r', encoding='utf-8') as json_file:
meta = json.load(json_file)
# pull the selectors out since we don't want them in the class definition right now
selectors = meta.pop(topconfig.selector_key, {})
selectors = meta.pop(topconfig.initial_selector_key, {})
meta['_name'] = name
meta['_path'] = path
class_logger = logging.getLogger(topconfig.CLIPPY_LOGNAME + '.' + name)
Expand All @@ -68,10 +68,9 @@ def _create_class(name: str, path: str):

# add the selectors
# this should be in the meta.json file.
for selector, desc in selectors.items():
setattr(cls, selector, Selector(None, selector))
sel = getattr(cls, selector)
sel.__doc__ = desc
for selector, docstr in selectors.items():
print(f'adding {selector} to class')
setattr(cls, selector, Selector(None, selector, docstr))
return cls


Expand Down Expand Up @@ -174,6 +173,12 @@ def m(self, *args, **kwargs):
# ~ statedesc.append(key)
# ~ setattr(self, key, statej[key])

if SELECTOR_KEY in outj:
for topsel, subsels in outj['SELECTOR_KEY'].items():
if not hasattr(self, topsel):
raise ClippyInvalidSelectorError(f'selector {topsel} not found in class; aborting')
self.getaddr(topsel)._import_from_dict(subsels)

# return result
if outj.get('returns_self', False):
return self
Expand Down
4 changes: 1 addition & 3 deletions src/clippy/backends/serialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,7 @@ def from_serial(cls, o: AnyDict):
raise ClippySerializationError("__clippy_type__.__class__ is unspecified")

if type_name not in config._dynamic_types:
raise ClippySerializationError(
f"\"{type_name}\" is not a known type, please clippy import it."
)
raise ClippySerializationError(f"\"{type_name}\" is not a known type, please clippy import it.")

# get the type to deserialize into from the _dynamic_types dict
# this does not account for the module the type may exist in
Expand Down
14 changes: 13 additions & 1 deletion src/clippy/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,16 @@
_dynamic_types: AnyDict = {}

# The name of the selector key in the meta.json files.
selector_key = 'selectors'
# these selectors cannot be dropped.
initial_selector_key = 'initial_selectors'
# The name in the json returns. If this key exists in the results
# then the entire selector hierarchy is replaced with this.
dynamic_selector_key = 'selectors'


# { "selectors:": {
# ("selector_name", "selector_desc"): {
# ("selector_name", "selector_desc"): {}
# (selector_name", "selector_desc"): {}
# }
# }}
2 changes: 1 addition & 1 deletion src/clippy/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
DRY_RUN_FLAG = '--clippy-validate'
JSON_FLAG = '--clippy-help'
STATE_KEY = '_state'
CLASS_KEY = '_class'
SELECTOR_KEY = '_selectors'
REAL = 'real'
STRING = 'string'
UINT = 'uint'
Expand Down
6 changes: 6 additions & 0 deletions src/clippy/error.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,9 @@ class ClippyTypeError(ClippyError):
'''
This error represents an error with the type of data being passed to the back end.
'''


class ClippyInvalidSelectorError(ClippyError):
'''
This error represents an error with a selector that is not defined for a given clippy class.
'''

0 comments on commit 7640f9c

Please sign in to comment.