Advertisement
mwchase

LC:Nobilis Update 2 - data.py only

May 7th, 2017
151
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 9.96 KB | None | 0 0
  1. import collections
  2. import functools
  3. import string
  4.  
  5.  
  6. ALLOWED_CHARS = set(string.letters + string.digits + '_')
  7. SENTINEL = object()
  8. PARENT = {}
  9.  
  10.  
  11. def visitor_name(cls):
  12.     node_type = getattr(cls, 'node_type', None)
  13.     if node_type is None:
  14.         raise ValueError(f'No node_type defined for {cls}')
  15.     return f'visit_{node_type}'
  16.  
  17.  
  18. class NodeMeta(type):
  19.  
  20.     def __new__(mcs, name, bases, namespace, **kwargs):
  21.         # Not sure this doesn't blow up.
  22.         cls = super().__new__(mcs, name, bases, namespace, **kwargs)
  23.         node_type = getattr(cls, 'node_type', SENTINEL)
  24.         if node_type is not SENTINEL:
  25.             if node_type is None:
  26.                 return cls
  27.             if node_type in cls._types:
  28.                 raise ValueError(f'Duplicate node type: {node_type!r}')
  29.             if not ALLOWED_CHARS.issuperset(node_type):
  30.                 raise ValueError(f'Impolite node type: {node_type!r}')
  31.             cls._types.add(node_type)
  32.             _visitor_name = visitor_name(cls)
  33.  
  34.             def dispatch(self, visitor):
  35.                 return getattr(visitor, _visitor_name)(self)
  36.             setattr(cls.Visitor, _visitor_name, cls.Visitor.default_visit)
  37.         else:
  38.             cls.node_type = None
  39.             cls._types = set()
  40.  
  41.             def dispatch(self, methods):
  42.                 raise NotImplementedError(
  43.                     'Subclasses should auto-generate this.')
  44.  
  45.             class Visitor:
  46.  
  47.                 def default_visit(self, node):
  48.                     raise NotImplementedError(
  49.                         f'No implementation for {node} on {self}.')
  50.  
  51.                 @classmethod
  52.                 def bind(cls_, node_class, function=SENTINEL):
  53.                     if cls_ is cls.Visitor:
  54.                         raise ValueError('Cannot bind base Visitor class.')
  55.                     if function is SENTINEL:
  56.                         return functools.partial(cls_.bind, node_class)
  57.                     setattr(cls_, visitor_name(node_class), function)
  58.                     return cls_
  59.  
  60.                 @classmethod
  61.                 def undefined(cls_):
  62.                     if cls_ is cls.Visitor:
  63.                         raise ValueError(
  64.                             'No point checking undefined on base Visitor '
  65.                             'class.')
  66.                     default = cls.Visitor.default_visit
  67.                     return {attr for attr in vars(cls.Visitor) if
  68.                             attr.startswith('visit_') and
  69.                             default == getattr(cls_, attr)}
  70.  
  71.             cls.Visitor = Visitor
  72.  
  73.         cls.dispatch = dispatch
  74.  
  75.         return cls
  76.  
  77.  
  78. class BusinessNode(metaclass=NodeMeta):
  79.  
  80.     __slots__ = ()
  81.  
  82.     @property
  83.     def parent(self):
  84.         return PARENT.get((type(self), self))
  85.  
  86.  
  87. class CachingMeta(type):
  88.  
  89.     def __get__(self, instance, owner):
  90.         if instance is None:
  91.             return functools.partial(self)
  92.         else:
  93.             return functools.partial(self, instance)
  94.  
  95.  
  96. class Caching(metaclass=CachingMeta):
  97.  
  98.     set_up = False
  99.  
  100.     def __new__(cls, instance, node):
  101.         return instance.setdefault((type(node), node), super().__new__(cls))
  102.  
  103.     def __init_subclass__(cls, **kwargs):
  104.         super().__init_subclass__(**kwargs)
  105.         init = vars(cls).get('__init__')
  106.         if init is not None:
  107.             def __init__(self, *args, **kwargs):
  108.                 if self.set_up:
  109.                     return
  110.                 init(self, *args, **kwargs)
  111.                 self.set_up = True
  112.             cls.__init__ = __init__
  113.  
  114.  
  115. class StateVisitor(BusinessNode.Visitor, dict):
  116.  
  117.     __slots__ = ()
  118.  
  119.  
  120. class UIVisitor(BusinessNode.Visitor, dict):
  121.  
  122.     __slots__ = 'state',
  123.  
  124.     def __init__(self, state):
  125.         super().__init__()
  126.         self.state = state
  127.  
  128.  
  129. class Choice(BusinessNode, collections.namedtuple('Choice', 'template')):
  130.  
  131.     node_type = 'choice'
  132.  
  133.  
  134. class ChoiceDisplay(BusinessNode,
  135.                     collections.namedtuple('ChoiceDisplay', 'choice')):
  136.  
  137.     node_type = 'choice_display'
  138.  
  139.  
  140. class ChoiceTaken(BusinessNode,
  141.                   collections.namedtuple('ChoiceTaken', 'choice')):
  142.  
  143.     node_type = 'choice_taken'
  144.  
  145.  
  146. class ChoiceListed(BusinessNode,
  147.                    collections.namedtuple('ChoiceListed', 'choice')):
  148.  
  149.     node_type = 'choice_listed'
  150.  
  151.  
  152. class ChoiceList(BusinessNode,
  153.                  collections.namedtuple('ChoiceList',
  154.                                         'choices initial_min initial_max')):
  155.  
  156.     node_type = 'choice_list'
  157.  
  158.     def __init__(self, choices, initial_min, initial_max):
  159.         for choice in choices:
  160.             key = type(choice), choice
  161.             if PARENT.setdefault(key, self) != self:
  162.                 for choice in choices:
  163.                     if PARENT.get(key) is self:
  164.                         del PARENT[key]
  165.                 raise ValueError(f'Cannot double-parent a choice: {choice}')
  166.  
  167.  
  168. class DisplayList(BusinessNode,
  169.                   collections.namedtuple('DisplayList',
  170.                                          'factory _choices _sections')):
  171.  
  172.     node_type = 'display_list'
  173.  
  174.     def __init__(self, factory, _choices, sections):
  175.         for choice in self.choices:
  176.             key = type(choice), choice
  177.             if PARENT.setdefault(key, self) != self:
  178.                 for choice in self.choices:
  179.                     if PARENT.get(key) is self:
  180.                         del PARENT[key]
  181.                 raise ValueError(f'Cannot double-parent a choice: {choice}')
  182.  
  183.     @property
  184.     def choices(self):
  185.         for choice in self._choices:
  186.             yield self.factory(choice)
  187.  
  188.     @property
  189.     def sections(self):
  190.         # This is O(n^2), but n is small
  191.         for section in self._sections:
  192.             yield Section(self, section[0])
  193.  
  194.  
  195. class Section(BusinessNode,
  196.               collections.namedtuple('Section', 'display_list', 'name')):
  197.  
  198.     def __init__(self, display_list, name):
  199.         self.section
  200.  
  201.     @property
  202.     def predicate(self):
  203.         return self.section[1]
  204.  
  205.     @property
  206.     def section(self):
  207.         for section in self.display_list._sections:
  208.             if section[0] == self.name:
  209.                 return section
  210.         raise ValueError(
  211.             f'No section in {self.display_list} matching {self.name}')
  212.  
  213.  
  214. class StateBase(Caching):
  215.  
  216.     visible = False
  217.  
  218.     def __init__(self, instance, node):
  219.         self.visitor = instance
  220.         self.node = node
  221.  
  222.     @property
  223.     def parent(self):
  224.         if self.node.parent:
  225.             return self.node.parent.dispatch(self.visitor)
  226.  
  227.  
  228. class BusinessButton(Caching):
  229.  
  230.     @property
  231.     def is_valid(self):
  232.         return self.enabled or not self.selected
  233.  
  234.     @property
  235.     def visible(self):
  236.         parent = self.parent
  237.         return parent is not None and parent.visible
  238.  
  239.  
  240. class ChoiceButton(BusinessButton):
  241.  
  242.     @property
  243.     def choice(self):
  244.         return self.node.choice.dispatch(self.visitor)
  245.  
  246.  
  247. @StateVisitor.bind(Choice)
  248. class StateVisitor(Caching):
  249.  
  250.     is_valid = True
  251.  
  252.     selected = False
  253.  
  254.     @property
  255.     def choice(self):
  256.         return self.node
  257.  
  258.  
  259. @StateVisitor.bind(ChoiceDisplay)
  260. class StateVisitor(ChoiceButton):
  261.  
  262.     enabled = False
  263.  
  264.     selected = False
  265.  
  266.     @property
  267.     def choice_display(self):
  268.         return self.node
  269.  
  270.  
  271. @StateVisitor.bind(ChoiceTaken)
  272. class StateVisitor(ChoiceButton):
  273.  
  274.     @property
  275.     def choice_taken(self):
  276.         return self.node
  277.  
  278.     # Should this go on UI instead?
  279.     @property
  280.     def visible(self):
  281.         return (super().visible and
  282.                 self.choice_taken.choice.dispatch(self.visitor).selected)
  283.  
  284.  
  285. @StateVisitor.bind(ChoiceListed)
  286. class StateVisitor(ChoiceButton):
  287.  
  288.     @property
  289.     def choice_listed(self):
  290.         return self.node
  291.  
  292.     @property
  293.     def selected(self):
  294.         return self.choice_listed.choice.dispatch(self.visitor).selected
  295.  
  296.     @property
  297.     def enabled(self):
  298.         parent = self.parent
  299.         if parent:
  300.             return self.selected or not parent.at_maximum
  301.         return True
  302.  
  303.  
  304. @StateVisitor.bind(ChoiceList)
  305. class StateVisitor(Caching):
  306.  
  307.     def __init__(self, instance, node):
  308.         super().__init__(instance, node)
  309.         self.minimum = node.initial_min
  310.         self.maximum = node.initial_max
  311.  
  312.     @property
  313.     def is_valid(self):
  314.         if self.maximum is not None and self.selected > self.maximum:
  315.             return False
  316.         return all(choice.is_valid for choice in self.choices)
  317.  
  318.     @property
  319.     def choices(self):
  320.         for choice in self.choice_list.choices:
  321.             yield choice.dispatch(self.visitor)
  322.  
  323.     @property
  324.     def selected(self):
  325.         return sum(choice.selected for choice in self.choices)
  326.  
  327.     @property
  328.     def choice_list(self):
  329.         return self.node
  330.  
  331.     @property
  332.     def at_minimum(self):
  333.         return self.selected >= self.minimum
  334.  
  335.     @property
  336.     def at_maximum(self):
  337.         return self.maximum is not None and self.selected >= self.maximum
  338.  
  339.  
  340. @StateVisitor.bind(DisplayList)
  341. class StateVisitor(Caching):
  342.  
  343.     @property
  344.     def display_list(self):
  345.         return self.node
  346.  
  347.     @property
  348.     def is_valid(self):
  349.         return all(choice.is_valid for choice in self.choices)
  350.  
  351.     @property
  352.     def choices(self):
  353.         for choice in self.display_list.choices:
  354.             yield choice.dispatch(self.visitor)
  355.  
  356.     @property
  357.     def sections(self):
  358.         for section in self.display_list.sections:
  359.             yield section.dispatch(self.visitor)
  360.  
  361.  
  362. @StateVisitor.bind(Section)
  363. class StateVisitor(Caching):
  364.  
  365.     @property
  366.     def section(self):
  367.         return self.node
  368.  
  369.     @property
  370.     def name(self):
  371.         return self.section.name
  372.  
  373.     @property
  374.     def choices(self):
  375.         predicate = self.section.predicate
  376.         for choice in self.section.display_list.choices:
  377.             visited = choice.dispatch(self.visitor)
  378.             if predicate(visited):
  379.                 yield visited
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement