Skip to content

Commit

Permalink
Add introspector module to extract information from defined Rebulk ob…
Browse files Browse the repository at this point in the history
…jects
  • Loading branch information
Toilal committed Nov 8, 2015
1 parent 34ff5b0 commit b620129
Show file tree
Hide file tree
Showing 7 changed files with 367 additions and 41 deletions.
125 changes: 125 additions & 0 deletions rebulk/introspector.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Introspect rebulk object to retrieve capabilities.
"""
from abc import ABCMeta, abstractproperty
from collections import defaultdict

import six
from .pattern import StringPattern, RePattern
from .utils import extend_safe


@six.add_metaclass(ABCMeta)
class Description(object):
"""
Abstract class for a description.
"""
@abstractproperty
def properties(self):
"""
Properties of described object.
:return: all properties that described object can generate grouped by name.
:rtype: dict
"""
pass


class PatternDescription(Description):
"""
Description of a pattern.
"""
def __init__(self, pattern):
self.pattern = pattern
self._properties = defaultdict(list)

if pattern.marker or pattern.private:
return
if pattern.properties:
for key, values in pattern.properties.items():
extend_safe(self._properties[key], values)
elif 'value' in pattern.match_options:
self._properties[pattern.name].append(pattern.match_options['value'])
elif isinstance(pattern, StringPattern):
extend_safe(self._properties[pattern.name], pattern.patterns)
elif isinstance(pattern, RePattern):
if pattern.name and not pattern.private_parent and pattern.name not in pattern.private_names:
extend_safe(self._properties[pattern.name], [None])
if not pattern.private_children:
for regex_pattern in pattern.patterns:
for group_name, values in regex_pattern.groupindex.items():
if group_name not in pattern.private_names:
extend_safe(self._properties[group_name], [None])


@property
def properties(self):
"""
Properties for this rule.
:return:
:rtype: dict
"""
return self._properties


class RuleDescription(Description):
"""
Description of a rule.
"""
def __init__(self, rule):
self.rule = rule

self._properties = defaultdict(list)

if rule.properties:
for key, values in rule.properties.items():
extend_safe(self._properties[key], values)

@property
def properties(self):
"""
Properties for this rule.
:return:
:rtype: dict
"""
return self._properties


class Introspection(Description):
"""
Introspection results.
"""
def __init__(self, rebulk, context=None):
self.patterns = [PatternDescription(pattern) for pattern in rebulk.effective_patterns(context)
if not pattern.private]
self.rules = [RuleDescription(rule) for rule in rebulk.effective_rules(context)]

@property
def properties(self):
"""
Properties for Introspection results.
:return:
:rtype:
"""
properties = defaultdict(list)
for pattern in self.patterns:
for key, values in pattern.properties.items():
extend_safe(properties[key], values)
for rule in self.rules:
for key, values in rule.properties.items():
extend_safe(properties[key], values)
return properties


def introspect(rebulk, context=None):
"""
Introspect a Rebulk instance to grab defined objects and properties that can be generated.
:param rebulk:
:type rebulk: Rebulk
:param context:
:type context:
:return: Introspection instance
:rtype: Introspection
"""
return Introspection(rebulk, context)
58 changes: 55 additions & 3 deletions rebulk/pattern.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ class Pattern(object):
"""

def __init__(self, name=None, tags=None, formatter=None, validator=None, children=False, every=False,
private_parent=False, private_children=False, private=False, marker=False, format_all=False,
validate_all=False, disabled=False, log_level=None):
private_parent=False, private_children=False, private=False, private_names=None, marker=False,
format_all=False, validate_all=False, disabled=False, log_level=None, properties=None):
"""
:param name: Name of this pattern
:type name: str
Expand All @@ -53,6 +53,8 @@ def __init__(self, name=None, tags=None, formatter=None, validator=None, childre
:type private_parent: bool
:param private_children: force return of children and flag children matches as private.
:type private_children: bool
:param private_names: force return of named matches as private.
:type private_names: bool
:param marker: flag this pattern as beeing a marker.
:type private: bool
:param format_all if True, pattern will format every match in the hierarchy (even match not yield).
Expand All @@ -63,15 +65,16 @@ def __init__(self, name=None, tags=None, formatter=None, validator=None, childre
:type disabled: bool|function
:param log_lvl: Log level associated to this pattern
:type log_lvl: int
"""
# pylint:disable=too-many-locals
self.name = name
self.tags = ensure_list(tags)
self.formatters, self._default_formatter = ensure_dict(formatter, lambda x: x)
self.validators, self._default_validator = ensure_dict(validator, lambda match: True)
self.every = every
self.children = children
self.private = private
self.private_names = private_names if private_names else []
self.private_parent = private_parent
self.private_children = private_children
self.marker = marker
Expand All @@ -82,6 +85,7 @@ def __init__(self, name=None, tags=None, formatter=None, validator=None, childre
else:
self.disabled = disabled
self._log_level = log_level
self._properties = properties
self.defined_at = debug.defined_at()

@property
Expand Down Expand Up @@ -162,6 +166,7 @@ def matches(self, input_string, context=None):
:return: matches based on input_string for this pattern
:rtype: iterator[Match]
"""

ret = []
for pattern in self.patterns:
yield_parent = self._yield_parent()
Expand All @@ -185,8 +190,22 @@ def matches(self, input_string, context=None):
if yield_children or self.private_children:
for child in match.children:
ret.append(child)
self._matches_privatize(ret)
return ret

def _matches_privatize(self, matches):
"""
Mark matches included in private_names with private flag.
:param matches:
:type matches:
:return:
:rtype:
"""
if self.private_names:
for child in matches:
if child.name in self.private_names:
child.private = True

@abstractproperty
def patterns(self): # pragma: no cover
"""
Expand All @@ -197,6 +216,27 @@ def patterns(self): # pragma: no cover
"""
pass

@property
def properties(self):
"""
Properties names and values that can ben retrieved by this pattern.
:return:
:rtype:
"""
if self._properties:
return self._properties
return {}

@abstractproperty
def match_options(self): # pragma: no cover
"""
dict of default options for generated Match objects
:return: **options to pass to Match constructor
:rtype: dict
"""
pass

@abstractmethod
def _match(self, pattern, input_string, context=None): # pragma: no cover
"""
Expand Down Expand Up @@ -234,6 +274,10 @@ def __init__(self, *patterns, **kwargs):
def patterns(self):
return self._patterns

@property
def match_options(self):
return self._match_kwargs

def _match(self, pattern, input_string, context=None):
for index in call(find_all, input_string, pattern, **self._kwargs):
yield call(Match, index, index + len(pattern), pattern=self, input_string=input_string,
Expand Down Expand Up @@ -276,6 +320,10 @@ def __init__(self, *patterns, **kwargs):
def patterns(self):
return self._patterns

@property
def match_options(self):
return self._match_kwargs

def _match(self, pattern, input_string, context=None):
names = {v: k for k, v in pattern.groupindex.items()}
for match_object in pattern.finditer(input_string):
Expand Down Expand Up @@ -315,6 +363,10 @@ def __init__(self, *patterns, **kwargs):
def patterns(self):
return self._patterns

@property
def match_options(self):
return self._match_kwargs

def _match(self, pattern, input_string, context=None):
ret = call(pattern, input_string, context, **self._kwargs)
if ret:
Expand Down
81 changes: 63 additions & 18 deletions rebulk/rebulk.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,21 @@ def matches(self, string, context=None):

return matches

def effective_rules(self, context=None):
"""
Get effective rules for this rebulk object and its children.
:param context:
:type context:
:return:
:rtype:
"""
rules = Rules()
rules.extend(self._rules)
for rebulk in self._rebulks:
if not rebulk.disabled(context):
extend_safe(rules, rebulk._rules)
return rules

def _execute_rules(self, matches, context):
"""
Execute rules for this rebulk and children.
Expand All @@ -248,13 +263,24 @@ def _execute_rules(self, matches, context):
:rtype:
"""
if not self.disabled(context):
rules = Rules()
rules.extend(self._rules)
for rebulk in self._rebulks:
if not rebulk.disabled(context):
extend_safe(rules, rebulk._rules)
rules = self.effective_rules(context)
rules.execute_all_rules(matches, context)

def effective_processors(self, context=None):
"""
Get effective processors for this rebulk object and its children.
:param context:
:type context:
:return:
:rtype:
"""
processors = []
for rebulk in self._rebulks:
if not rebulk.disabled(context):
extend_safe(processors, rebulk._processors)
extend_safe(processors, self._processors)
return processors

def _execute_processors(self, matches, context):
"""
Execute processors for this rebulk and children.
Expand All @@ -266,16 +292,28 @@ def _execute_processors(self, matches, context):
:rtype:
"""
if not self.disabled(context):
processors = list(self._processors)
for rebulk in self._rebulks:
if not rebulk.disabled(context):
extend_safe(processors, rebulk._processors)
processors = self.effective_processors(context)
for func in processors:
ret = call(func, matches, context)
if isinstance(ret, Matches):
matches = ret
return matches

def effective_post_processors(self, context=None):
"""
Get effective post processors for this rebulk object and its children.
:param context:
:type context:
:return:
:rtype:
"""
post_processors = []
for rebulk in self._rebulks:
if not rebulk.disabled(context):
extend_safe(post_processors, rebulk._post_processors)
extend_safe(post_processors, self._post_processors)
return post_processors

def _execute_post_processors(self, matches, context):
"""
Execute post processors for this rebulk and children.
Expand All @@ -287,17 +325,27 @@ def _execute_post_processors(self, matches, context):
:rtype:
"""
if not self.disabled(context):
post_processors = []
for rebulk in self._rebulks:
if not rebulk.disabled(context):
extend_safe(post_processors, rebulk._post_processors)
extend_safe(post_processors, self._post_processors)
post_processors = self.effective_post_processors(context)
for func in post_processors:
ret = call(func, matches, context)
if isinstance(ret, Matches):
matches = ret
return matches

def effective_patterns(self, context=None):
"""
Get effective patterns for this rebulk object and its children.
:param context:
:type context:
:return:
:rtype:
"""
patterns = list(self._patterns)
for rebulk in self._rebulks:
if not rebulk.disabled(context):
extend_safe(patterns, rebulk._patterns)
return patterns

def _matches_patterns(self, matches, context):
"""
Search for all matches with current paterns agains input_string
Expand All @@ -309,10 +357,7 @@ def _matches_patterns(self, matches, context):
:rtype:
"""
if not self.disabled(context):
patterns = list(self._patterns)
for rebulk in self._rebulks:
if not rebulk.disabled(context):
extend_safe(patterns, rebulk._patterns)
patterns = self.effective_patterns(context)
for pattern in patterns:
if not pattern.disabled(context):
pattern_matches = pattern.matches(matches.input_string, context)
Expand Down
Loading

0 comments on commit b620129

Please sign in to comment.