* [gentoo-commits] proj/R_overlay:master commit in: /, roverlay/packagerules/parser/context/, roverlay/packagerules/parser/, ...
@ 2013-02-05 17:48 André Erdmann
0 siblings, 0 replies; only message in thread
From: André Erdmann @ 2013-02-05 17:48 UTC (permalink / raw
To: gentoo-commits
commit: 93d0f65e1c0915c76a09e858b6d7533958063b70
Author: André Erdmann <dywi <AT> mailerd <DOT> de>
AuthorDate: Tue Feb 5 17:43:12 2013 +0000
Commit: André Erdmann <dywi <AT> mailerd <DOT> de>
CommitDate: Tue Feb 5 17:45:44 2013 +0000
URL: http://git.overlays.gentoo.org/gitweb/?p=proj/R_overlay.git;a=commit;h=93d0f65e
Introducing package rules, part 2
This commit adds the following functionality to the packagerules module:
* load rules from files (using a new syntax)
* add logging capabilities to Rules/Acceptors/Actions
* misc fixups / changes
* add new modules to setup.py
* an example package rules file in config/package_rules
---
config/package_rules | 190 +++++++++++++++++
roverlay/packagerules/abstract/acceptors.py | 152 ++++++++++++++-
roverlay/packagerules/abstract/actions.py | 5 +
roverlay/packagerules/abstract/rules.py | 172 ++++++++++++----
roverlay/packagerules/acceptors/__init__.py | 5 +
roverlay/packagerules/acceptors/stringmatch.py | 147 ++++++++++++++
roverlay/packagerules/acceptors/trivial.py | 47 +++++
roverlay/packagerules/acceptors/util.py | 25 +++
roverlay/packagerules/actions/evar.py | 7 +
roverlay/packagerules/loader.py | 33 ---
roverlay/packagerules/parser/__init__.py | 5 +
roverlay/packagerules/parser/context/__init__.py | 5 +
roverlay/packagerules/parser/context/action.py | 90 ++++++++
roverlay/packagerules/parser/context/base.py | 51 +++++
roverlay/packagerules/parser/context/match.py | 235 ++++++++++++++++++++++
roverlay/packagerules/parser/context/rule.py | 201 ++++++++++++++++++
roverlay/packagerules/parser/namespace.py | 65 ++++++
roverlay/packagerules/parser/text.py | 110 ++++++++++
roverlay/packagerules/rules.py | 54 ++++--
setup.py | 6 +-
20 files changed, 1506 insertions(+), 99 deletions(-)
diff --git a/config/package_rules b/config/package_rules
new file mode 100644
index 0000000..560d4d0
--- /dev/null
+++ b/config/package_rules
@@ -0,0 +1,190 @@
+# roverlay package rules reference
+#
+# !!! draft / todo
+#
+# (Concrete examples: scroll down)
+#
+#
+# ========================
+# Package Rule File Syntax
+# ========================
+#
+# Each rule consists of a match- and an action block
+#
+# The basic syntax is <<<
+#
+# MATCH:
+# <match statement 1>
+# <match statement 2>
+# ...
+# <match statement n>
+# ACTION:
+# <action statement 1>
+# <action statement 2>
+# ...
+# <action statement n>
+# END;
+#
+# >>>
+#
+# As usual, leading whitespace is optional and will be ignored.
+#
+# ------------
+# Match blocks
+# ------------
+#
+# A match block consists of one or more conditions ("match statements")
+# under which a rule applies its actions to a package.
+# It can also contain nested blocks representing a boolean function
+# (AND, OR, NOR, XOR1; syntax: see below)
+# Such "boolean blocks" will _not_ be optimized, so (as usual) be careful
+# with adding unnecessary blocks.
+# The top-level logic for a match block is AND.
+#
+# << copy-paste from roverlay/packagerules/abstract/acceptors.py >>
+# Note:
+# There's no Acceptor_NOT class.
+# How would you define a multi-input function "not :: [Acceptor] -> Bool"?
+# In most cases, you want "none of the listed Acceptors should match",
+# which is exactly the definition of Acceptor_NOR.
+# << end c-p >>
+#
+# Match statement syntax
+# ----------------------
+#
+# Nested match statements / boolean blocks:
+#
+# <boolean function>
+# * <match statement 1>
+# * <match statement 2>
+# * ...
+# * <match statement n>
+#
+# The leading asterisk chars '*' are important and indicate the match depth.
+# For a match depth > 1 they have to be combined into a single string, e.g.
+# "**" for a match depth of 2.
+# As an alternative to the asterisk char, dash chars '-' can also be used
+# (they're interchangeable).
+#
+# A less abstract example that realizes
+#
+# f :: (<match statement>^4) -> <match statement>
+# f (a,b,c,d) := XOR1 ( c, OR ( a, b, AND ( c, d ) ), NOR ( a, d ), b )
+#
+# is <<<
+#
+# xor1
+# * c
+# * or
+# ** a
+# ** b
+# ** and
+# *** c
+# *** d
+# ** nor
+# *** a
+# *** d
+# * b
+#
+# >>>
+#
+# boolean expressions: keywords
+#
+# +======+===============+
+# | func | keywords |
+# +======+===============+
+# | AND | and, all, && |
+# +------+---------------+
+# | OR | or, || |
+# +------+---------------+
+# | XOR1 | xor1, xor, ^^ |
+# +------+---------------+
+# | NOR | nor, none |
+# +------+---------------+
+#
+# * these keywords are case sensitive
+#
+#
+# "normal" match statements:
+#
+# A normal match statement consists of a keyword, an operator (optional) and
+# a value ("argv for the keyword").
+#
+# +===============+=============+====================================+
+# | operator name | operator(s) | description |
+# +===============+=============+====================================+
+# | exact-string | == = | exact string match |
+# +---------------+-------------+------------------------------------+
+# | nocase-string | ,= =, | case-insensitive string match |
+# +---------------+-------------+------------------------------------+
+# | exact-regex | ~= =~ | exact regex match (^<expression>$) |
+# +---------------+-------------+------------------------------------+
+# | regex | ~~ ~ | partial regex match |
+# +---------------+-------------+------------------------------------+
+#
+#
+# +==============+==================+================================+
+# | keyword | default operator | description |
+# +==============+==================+================================+
+# | repo | nocase-string | alias to repo_name |
+# +--------------+------------------+--------------------------------+
+# | repo_name | nocase-string | name of the repo, e.g. 'CRAN' |
+# +--------------+------------------+--------------------------------+
+# | package | implicit | package name + version |
+# +--------------+------------------+--------------------------------+
+# | package_name | implicit | the package name (package file |
+# | | | name without version and file |
+# | | | extension) |
+# +--------------+------------------+--------------------------------+
+# | name | implicit | alias to package_name |
+# +--------------+------------------+--------------------------------+
+#
+# implicit operator: exact-regex if any wildcard char ('?','*') in string
+# else exact-string (wildcards will be replaced when
+# using the implicit op.)
+#
+#
+# -------------
+# Action blocks
+# -------------
+#
+# action keywords
+# +================+============+==========================+
+# | keyword | has value? | description |
+# +================+============+==========================+
+# | ignore | no | ignore package |
+# +----------------+------------+--------------------------+
+# | do-not-process | no | ignore package |
+# +----------------+------------+--------------------------+
+# | keywords | yes | set per-package KEYWORDS |
+# +----------------+------------+--------------------------+
+#
+# TODO;
+#
+#
+# ========
+# Examples
+# ========
+#
+# ignore all packages starting with R <<<
+#
+# MATCH:
+# package_name R*
+# ACTION:
+# ignore
+# END;
+#
+# >>>
+#
+# set KEYWORDS to "-x86 amd64" for all packages from CRAN that have "x86_64"
+# or "amd64" in their name
+#
+# MATCH:
+# repo == CRAN
+# or
+# * package ~ x86_64
+# * package ~ amd64
+# ACTION:
+# keywords "-x86 amd64"
+# END;
+#
diff --git a/roverlay/packagerules/abstract/acceptors.py b/roverlay/packagerules/abstract/acceptors.py
index fa28dd0..4533f37 100644
--- a/roverlay/packagerules/abstract/acceptors.py
+++ b/roverlay/packagerules/abstract/acceptors.py
@@ -4,20 +4,40 @@
# Distributed under the terms of the GNU General Public License;
# either version 2 of the License, or (at your option) any later version.
+"""
+Classes provided by this module:
+* Acceptor -- base class for all acceptors
+* ValueMatchAcceptor -- base class for acceptors that compare a value
+* _AcceptorCompound -- base class combines one more more acceptors
+ and represents a boolean term
+ IOW, they realize a function "[Acceptor] -> Bool"
+* Acceptor_<type> -- specific _AcceptorCompound classes
+-> Acceptor_AND
+-> Acceptor_OR
+-> Acceptor_XOR1
+-> Acceptor_NOR
+
+Note:
+ There's no Acceptor_NOT class.
+ How would you define a multi-input function "not :: [Acceptor] -> Bool"?
+ In most cases, you want "none of the listed Acceptors should match",
+ which is exactly the definition of Acceptor_NOR.
+"""
+
import roverlay.util
__all__ = [
- 'Acceptor',
+ 'Acceptor', 'ValueMatchAcceptor',
'Acceptor_AND', 'Acceptor_OR', 'Acceptor_XOR1', 'Acceptor_NOR',
]
-class EmptyAcceptor ( ValueError ):
+class EmptyAcceptorError ( ValueError ):
"""
Exception for "empty" Acceptors (Acceptor represents a boolean
expression, but has no Acceptors attached).
"""
pass
-# --- end of EmptyAcceptor ---
+# --- end of EmptyAcceptorError ---
class Acceptor ( object ):
@@ -28,8 +48,13 @@ class Acceptor ( object ):
def __init__ ( self, priority ):
super ( Acceptor, self ).__init__()
self.priority = priority
+ self.logger = None
# --- end of __init__ (...) ---
+ def set_logger ( self, logger ):
+ self.logger = logger.getChild ( self.__class__.__name__ )
+ # --- end of logger (...) ---
+
def prepare ( self ):
"""Prepare the Acceptor for usage (typically used after loading
it from a file).
@@ -47,8 +72,33 @@ class Acceptor ( object ):
raise NotImplementedError()
# --- end of accepts (...) ---
+ def _get_gen_str_indent ( self, level, match_level ):
+ """Returns the common prefix used in gen_str().
+
+ arguments:
+ * level
+ * match_level
+ """
+ if match_level > 1:
+ return ( level * ' ' + ( match_level - 1 ) * '*' + ' ' )
+ else:
+ return level * ' '
+ # --- end of _get_gen_str_indent (...) ---
+
+ def gen_str ( self, level, match_level ):
+ """Yields text lines (str) that represent this match statement in
+ text file format.
+
+ arguments:
+ * level -- indention level
+ * match_level -- match statement level
+ """
+ raise NotImplementedError()
+ # --- end of gen_str (...) ---
+
# --- end of Acceptor ---
+
class _AcceptorCompound ( Acceptor ):
"""The base class for Acceptors that represent a boolean expression."""
@@ -57,20 +107,26 @@ class _AcceptorCompound ( Acceptor ):
self._acceptors = list()
# --- end of __init__ (...) ---
+ def set_logger ( self, logger ):
+ super ( _AcceptorCompound, self ).set_logger ( logger )
+ for acceptor in self._acceptors:
+ acceptor.set_logger ( self.logger )
+ # --- end of set_logger (...) ---
+
def prepare ( self ):
"""Prepare the Acceptor for usage (typically used after loading
it from a file).
Sorts all Acceptors according to their priority.
- Raises: EmptyAcceptor
+ Raises: EmptyAcceptorError
"""
if len ( self._acceptors ) > 0:
self._acceptors = roverlay.util.priosort ( self._acceptors )
for acceptor in self._acceptors:
acceptor.prepare()
else:
- raise EmptyAcceptor()
+ raise EmptyAcceptorError()
# --- end of prepare (...) ---
def add_acceptor ( self, acceptor ):
@@ -82,8 +138,33 @@ class _AcceptorCompound ( Acceptor ):
self._acceptors.append ( acceptor )
# --- end of add_acceptor (...) ---
+ def gen_str ( self, level, match_level ):
+ if match_level > 0:
+ yield (
+ self._get_gen_str_indent ( level, match_level )
+ + self.__class__.__name__[9:].lower()
+ )
+
+ for acceptor in self._acceptors:
+ for s in acceptor.gen_str (
+ level=( level + 1 ), match_level=( match_level + 1 )
+ ):
+ yield s
+ else:
+ # top-level match block
+ # * do not print "and"/"or"/...
+ # * do not increase indent level
+
+ for acceptor in self._acceptors:
+ for s in acceptor.gen_str (
+ level=level, match_level=( match_level + 1 )
+ ):
+ yield s
+ # --- end of gen_str (...) ---
+
# --- end of _AcceptorCompound ---
+
class Acceptor_OR ( _AcceptorCompound ):
"""OR( <Acceptors> )"""
@@ -101,6 +182,7 @@ class Acceptor_OR ( _AcceptorCompound ):
# --- end of Acceptor_OR ---
+
class Acceptor_AND ( _AcceptorCompound ):
"""AND( <Acceptors> )"""
@@ -118,6 +200,7 @@ class Acceptor_AND ( _AcceptorCompound ):
# --- end of Acceptor_AND ---
+
class Acceptor_XOR1 ( _AcceptorCompound ):
"""XOR( <Acceptors> )"""
@@ -142,6 +225,7 @@ class Acceptor_XOR1 ( _AcceptorCompound ):
# --- end of Acceptor_XOR1 ---
+
class Acceptor_NOR ( Acceptor_OR ):
"""NOR( <Acceptors> )"""
@@ -155,3 +239,61 @@ class Acceptor_NOR ( Acceptor_OR ):
# --- end of accepts (...) ---
# --- end of Acceptor_NOR ---
+
+
+class ValueMatchAcceptor ( Acceptor ):
+ """
+ Base class for Acceptors that accept PackageInfo instances with a certain
+ value (e.g. repo name, package name).
+ """
+
+ def __init__ ( self, priority, get_value ):
+ """Constructor for ValueMatchAcceptor.
+
+ arguments:
+ * priority -- priority of this Acceptor
+ * get_value -- function F(<PackageInfo>) that is used to get the
+ value
+ """
+ super ( ValueMatchAcceptor, self ).__init__ ( priority=priority )
+ self._get_value = get_value
+ # --- end of __init__ (...) ---
+
+ def _matches ( self, value ):
+ """Returns true if this acceptor matches the given value.
+
+ arguments:
+ * value --
+ """
+ raise NotImplementedError()
+ # --- end of _matches (...) ---
+
+ def accepts ( self, p_info ):
+ compare_to = self._get_value ( p_info )
+ if self._matches ( compare_to ):
+ self.logger.debug ( "accepts {}".format ( compare_to ) )
+ return True
+ else:
+ return False
+ # --- end of accepts (...) ---
+
+ def _get_value_name ( self ):
+ """Returns the name that describes the value which this acceptor is
+ comparing to, e.g. "repo_name" or "package".
+
+ Meant for usage in gen_str().
+ """
+ if hasattr ( self._get_value, 'func_name' ):
+ n = self._get_value.func_name
+ elif hasattr ( self._get_value, '__name__' ):
+ n = self._get_value.__name__
+ else:
+ return str ( self._get_value )
+
+ if n[:4] == 'get_':
+ return n[4:] or n
+ else:
+ return n
+ # --- end of _get_value_name (...) ---
+
+# --- end of ValueMatchAcceptor ---
diff --git a/roverlay/packagerules/abstract/actions.py b/roverlay/packagerules/abstract/actions.py
index 1204f53..6e9d946 100644
--- a/roverlay/packagerules/abstract/actions.py
+++ b/roverlay/packagerules/abstract/actions.py
@@ -12,8 +12,13 @@ class PackageRuleAction ( object ):
def __init__ ( self, priority=1000 ):
super ( PackageRuleAction, self ).__init__()
self.priority = priority
+ self.logger = None
# --- end of __init__ (...) ---
+ def set_logger ( self, logger ):
+ self.logger = logger
+ # --- end of set_logger (...) ---
+
def apply_action ( self, p_info ):
"""Applies the action to the given PackageInfo.
diff --git a/roverlay/packagerules/abstract/rules.py b/roverlay/packagerules/abstract/rules.py
index b9e057c..80a7d62 100644
--- a/roverlay/packagerules/abstract/rules.py
+++ b/roverlay/packagerules/abstract/rules.py
@@ -8,7 +8,94 @@ import roverlay.util
__all__ = [ 'PackageRule', 'NestedPackageRule', 'IgnorePackageRule', ]
-class PackageRule ( object ):
+
+class IgnorePackageRule ( object ):
+ """A rule that has only one action: filter packages."""
+
+ def __init__ ( self, priority=100 ):
+ super ( IgnorePackageRule, self ).__init__()
+ self.priority = priority
+ self._acceptor = None
+ self.logger = None
+ # --- end of __init__ (...) ---
+
+ def accepts ( self, p_info ):
+ """Returns True if this rule matches the given PackageInfo else False.
+
+ arguments:
+ * p_info --
+ """
+ return self._acceptor.accepts ( p_info )
+ # --- end of accepts (...) ---
+
+ def set_acceptor ( self, acceptor ):
+ """Assigns an acceptor to this rule.
+ Such objects are used to match PackageInfo instances (in self.accepts()).
+
+ arguments:
+ * acceptor --
+ """
+ self._acceptor = acceptor
+ # --- end of set_acceptor (...) ---
+
+ def set_logger ( self, logger ):
+ """Assigns a logger to this package rule.
+
+ arguments:
+ * logger --
+ """
+ self.logger = logger
+ if hasattr ( self, '_acceptor' ):
+ self._acceptor.set_logger ( self.logger )
+ # --- end of set_logger (...) ---
+
+ def apply_actions ( self, p_info ):
+ """Ignores a PackageInfo by returning False.
+
+ arguments:
+ * p_info --
+ """
+ return False
+ # --- end of apply_actions (...) ---
+
+ def prepare ( self ):
+ """
+ Prepares this rule for usage. Has to be called after adding actions.
+ """
+ if hasattr ( self, '_acceptor' ):
+ self._acceptor.prepare()
+ # --- end of prepare (...) ---
+
+ def _gen_action_str ( self, level ):
+ yield level * ' ' + 'ignore'
+ # --- end of _gen_action_str (...) ---
+
+ def gen_str ( self, level ):
+ indent = level * ' '
+
+ yield ( indent + 'MATCH:' )
+ for s in self._acceptor.gen_str ( level=( level + 1 ), match_level=0 ):
+ yield s
+
+ yield ( indent + 'ACTION:' )
+ for s in self._gen_action_str ( level=( level + 1 ) ):
+ yield s
+
+ if hasattr ( self, '_gen_rules_str' ):
+ for s in self._gen_rules_str ( level=( level + 1 ) ):
+ yield s
+
+ yield ( indent + 'END;' )
+ # --- end of gen_str (...) ---
+
+ def __str__ ( self ):
+ return '\n'.join ( self.gen_str ( level=0 ) )
+ # --- end of __str__ (...) ---
+
+# --- end of IgnorePackageRule ---
+
+
+class PackageRule ( IgnorePackageRule ):
"""A package rule is able to determine whether it matches
a given PackageInfo instance (using Acceptor instances)
and applies zero or more actions (using PackageAction instances) to the
@@ -16,33 +103,29 @@ class PackageRule ( object ):
"""
def __init__ ( self, priority=1000 ):
- super ( PackageRule, self ).__init__()
- self.priority = priority
- self._actions = list()
- self._acceptors = list()
+ super ( PackageRule, self ).__init__( priority )
+ self._actions = list()
# --- end of __init__ (...) ---
def prepare ( self ):
"""
Prepares this rule for usage. Has to be called after adding actions.
"""
- self._actions = roverlay.util.priosort ( self._actions )
- self._acceptors = roverlay.util.priosort ( self._acceptors )
- for acceptor in self._acceptors:
- acceptor.prepare()
+ super ( PackageRule, self ).prepare()
+ self._actions = roverlay.util.priosort ( self._actions )
# --- end of prepare (...) ---
- def accepts ( self, p_info ):
- """Returns True if this rule matches the given PackageInfo else False.
+ def set_logger ( self, logger ):
+ """Assigns a logger to this package rule and all actions.
arguments:
- * p_info --
+ * logger --
"""
- for acceptor in self._acceptors:
- if not acceptor.accepts ( p_info ):
- return False
- return True
- # --- end of accepts (...) ---
+ super ( PackageRule, self ).set_logger ( logger )
+ action_logger = self.logger.getChild ( 'Action' )
+ for action in self._actions:
+ action.set_logger ( action_logger )
+ # --- end of set_logger (...) ---
def apply_actions ( self, p_info ):
"""Applies all actions to the given PackageInfo.
@@ -69,38 +152,15 @@ class PackageRule ( object ):
self._actions.append ( action )
# --- end of add_action (...) ---
- def add_acceptor ( self, acceptor ):
- """Adds an acceptor to this rule. Such objects are used to match
- PackageInfo instances (in self.accepts()).
-
- arguments:
- * acceptor
- """
- self._acceptors.append ( acceptor )
- # --- end of add_acceptor (...) ---
+ def _gen_action_str ( self, level ):
+ for x in self._actions:
+ for s in x.gen_str ( level=level ):
+ yield s
+ # --- end of _gen_action_str (...) ---
# --- end of PackageRule ---
-class IgnorePackageRule ( PackageRule ):
- """A rule that has only one action: filter packages."""
-
- def __init__ ( self, priority=100 ):
- super ( PackageRule, self ).__init__( priority )
- # --- end of __init__ (...) ---
-
- def apply_actions ( self, p_info ):
- """Ignores a PackageInfo by returning False.
-
- arguments:
- * p_info --
- """
- return False
- # --- end of apply_actions (...) ---
-
-# --- end of IgnorePackageRule ---
-
-
class NestedPackageRule ( PackageRule ):
"""A rule that consists of zero or more subordinate rules."""
@@ -109,6 +169,28 @@ class NestedPackageRule ( PackageRule ):
self._rules = list()
# --- end of __init__ (...) ---
+ def _gen_rules_str ( self, level ):
+ for rule in self._rules:
+ for s in rule.gen_str ( level ):
+ yield s
+ # --- end of _gen_rules_str (...) ---
+
+ def set_logger ( self, logger ):
+ """Assigns a logger to this package rule and all actions.
+
+ arguments:
+ * logger --
+ """
+ super ( NestedPackageRule, self ).set_logger ( logger )
+ if hasattr ( self, 'is_toplevel' ) and self.is_toplevel:
+ nested_logger = self.logger.getChild ( 'nested' )
+ for nested_rule in self._rules:
+ nested_rule.set_logger ( nested_logger )
+ else:
+ for nested_rule in self._rules:
+ nested_rule.set_logger ( self.logger )
+ # --- end of set_logger (...) ---
+
def prepare ( self ):
"""
Prepares this rule for usage. Has to be called after adding actions.
diff --git a/roverlay/packagerules/acceptors/__init__.py b/roverlay/packagerules/acceptors/__init__.py
new file mode 100644
index 0000000..8a3d9ca
--- /dev/null
+++ b/roverlay/packagerules/acceptors/__init__.py
@@ -0,0 +1,5 @@
+# R overlay -- package rules, acceptors
+# -*- coding: utf-8 -*-
+# Copyright (C) 2013 André Erdmann <dywi@mailerd.de>
+# Distributed under the terms of the GNU General Public License;
+# either version 2 of the License, or (at your option) any later version.
diff --git a/roverlay/packagerules/acceptors/stringmatch.py b/roverlay/packagerules/acceptors/stringmatch.py
new file mode 100644
index 0000000..df7edb5
--- /dev/null
+++ b/roverlay/packagerules/acceptors/stringmatch.py
@@ -0,0 +1,147 @@
+# R overlay -- package rules, value/string acceptors
+# -*- coding: utf-8 -*-
+# Copyright (C) 2013 André Erdmann <dywi@mailerd.de>
+# Distributed under the terms of the GNU General Public License;
+# either version 2 of the License, or (at your option) any later version.
+
+import re
+
+import roverlay.packagerules.abstract.acceptors
+
+class ValueAcceptor (
+ roverlay.packagerules.abstract.acceptors.ValueMatchAcceptor
+):
+ """Exact value match Acceptor."""
+
+ def __init__ ( self, priority, get_value, value ):
+ """Constructor for ValueAcceptor.
+
+ arguments:
+ * priority -- priority of this Acceptor
+ * get_value -- function that returns a value
+ * _str -- string that should match that^ value
+ """
+ super ( ValueAcceptor, self ).__init__ (
+ priority = priority,
+ get_value = get_value
+ )
+ self._value = value
+ # --- end of __init__ (...) ---
+
+ def _matches ( self, value ):
+ return self._value == value
+ # --- end of _matches (...) ---
+
+ def gen_str ( self, level, match_level ):
+ yield (
+ self._get_gen_str_indent ( level, match_level )
+ + self._get_value_name() + ' == ' + str ( self._value )
+ )
+ # --- end of gen_str (...) ---
+
+# --- end of ValueAcceptor ---
+
+
+class StringAcceptor ( ValueAcceptor ):
+ """Exact string match Acceptor."""
+ pass
+
+# --- end of StringAcceptor ---
+
+
+class NocaseStringAcceptor ( StringAcceptor ):
+ """Case-insensitive string match Acceptor."""
+
+ def __init__ ( self, priority, get_value, _str ):
+ super ( NocaseStringAcceptor, self ).__init__ (
+ priority = priority,
+ get_value = get_value,
+ value = _str.lower()
+ )
+ # --- end of __init__ (...) ---
+
+ def _matches ( self, value ):
+ return self._value == value.lower()
+ # --- end of _matches (...) ---
+
+ def gen_str ( self, level, match_level ):
+ yield (
+ self._get_gen_str_indent ( level, match_level )
+ + self._get_value_name() + ' ,= ' + self._value
+ )
+ # --- end of gen_str (...) ---
+
+# --- end of NocaseStringAcceptor ---
+
+
+class RegexAcceptor (
+ roverlay.packagerules.abstract.acceptors.ValueMatchAcceptor
+):
+ """Regex match Acceptor."""
+
+ def __init__ ( self, priority, get_value, regex=None, regex_compiled=None ):
+ """Constructor for RegexAcceptor.
+
+ arguments:
+ * priority -- see _ValueMatchAcceptor
+ * get_value -- see _ValueMatchAcceptor
+ * regex -- a regex string
+ * regex_compiled -- a compiled regex
+
+ Either regex or regex_compiled must be specified, else an exception
+ is raised.
+ """
+ super ( RegexAcceptor, self ).__init__ (
+ priority = priority,
+ get_value = get_value
+ )
+
+ # exactly-one-of (regex, regex_compiled)
+ if bool ( regex ) == bool ( regex_compiled ):
+ raise Exception (
+ "either 'regex' or 'regex_compiled' must be passed to {}".format (
+ self.__class__.__name__
+ )
+ )
+
+ self._regex = regex_compiled if regex_compiled else re.compile ( regex )
+ # --- end of __init__ (...) ---
+
+ def _matches ( self, value ):
+ return bool ( self._regex.search ( value ) )
+ # --- end of _matches (...) ---
+
+ def gen_str ( self, level, match_level ):
+ yield (
+ self._get_gen_str_indent ( level, match_level )
+ + self._get_value_name() + ' ~ ' + self._regex.pattern
+ )
+ # --- end of gen_str (...) ---
+
+# --- end of RegexAcceptor ---
+
+
+class ExactRegexAcceptor ( RegexAcceptor ):
+ """Exact regex match Acceptor (matches "^<regex>$")."""
+
+ def __init__ ( self, priority, get_value, regex ):
+ super ( ExactRegexAcceptor, self ).__init__ (
+ priority = priority,
+ get_value = get_value,
+ regex = '^' + regex + '$'
+ )
+ # --- end of __init__ (...) ---
+
+ def _matches ( self, value ):
+ # using re.match instead of re.search
+ return bool ( self._regex.match ( value ) )
+ # --- end of _matches (...) ---
+
+ def gen_str ( self, level, match_level ):
+ yield (
+ self._get_gen_str_indent ( level, match_level )
+ + self._get_value_name() + ' ~= ' + self._regex.pattern
+ )
+ # --- end of gen_str (...) ---
+
+# --- end of ExactRegexAcceptor ---
diff --git a/roverlay/packagerules/acceptors/trivial.py b/roverlay/packagerules/acceptors/trivial.py
new file mode 100644
index 0000000..72d3ee1
--- /dev/null
+++ b/roverlay/packagerules/acceptors/trivial.py
@@ -0,0 +1,47 @@
+# R overlay -- package rules, trivial acceptors
+# -*- coding: utf-8 -*-
+# Copyright (C) 2013 André Erdmann <dywi@mailerd.de>
+# Distributed under the terms of the GNU General Public License;
+# either version 2 of the License, or (at your option) any later version.
+
+"""
+This module provides so-called trivial acceptors.
+
+An acceptor is trivial if (and only if) the return value of its accepts()
+function does not depend on any arg (i.e. the PackageInfo instance).
+In most cases the return value is known a priori.
+(An exception would be a "RandomAcceptor" class.)
+"""
+
+import roverlay.packagerules.abstract.acceptors
+
+class TrueAcceptor ( roverlay.packagerules.abstract.acceptors.Acceptor ):
+
+ def accepts ( self, *a, **b ):
+ return True
+ # --- end of accepts (...) ---
+
+ def gen_str ( self, level, match_level ):
+ yield (
+ self._get_gen_str_indent ( level, match_level )
+ + 'any'
+ )
+ # --- end of gen_str (...) ---
+
+# --- end of TrueAcceptor ---
+
+
+class FalseAcceptor ( roverlay.packagerules.abstract.acceptors.Acceptor ):
+
+ def accepts ( self, *a, **b ):
+ return False
+ # --- end of accepts (...) ---
+
+ def gen_str ( self, level, match_level ):
+ yield (
+ self._get_gen_str_indent ( level, match_level )
+ + 'none'
+ )
+ # --- end of gen_str (...) ---
+
+# --- end of FalseAcceptor ---
diff --git a/roverlay/packagerules/acceptors/util.py b/roverlay/packagerules/acceptors/util.py
new file mode 100644
index 0000000..1fa8798
--- /dev/null
+++ b/roverlay/packagerules/acceptors/util.py
@@ -0,0 +1,25 @@
+# R overlay -- package rules, utility functions for acceptors
+# -*- coding: utf-8 -*-
+# Copyright (C) 2013 André Erdmann <dywi@mailerd.de>
+# Distributed under the terms of the GNU General Public License;
+# either version 2 of the License, or (at your option) any later version.
+
+import roverlay.packageinfo
+
+# Functions that return package info values
+# * accessing p_info._info directly here
+
+def get_repo_name ( p_info ):
+ return p_info._info ['origin'].name
+# --- end of get_repo_name (...) ---
+
+def get_package ( p_info ):
+ # package name with version
+ return roverlay.packageinfo.PackageInfo.PKGSUFFIX_REGEX.sub (
+ '', p_info._info ['package_filename']
+ )
+# --- end of get_package (...) ---
+
+def get_package_name ( p_info ):
+ return p_info._info ['package_name']
+# --- end of get_package_name (...) ---
diff --git a/roverlay/packagerules/actions/evar.py b/roverlay/packagerules/actions/evar.py
index d2a4580..00cefab 100644
--- a/roverlay/packagerules/actions/evar.py
+++ b/roverlay/packagerules/actions/evar.py
@@ -44,6 +44,13 @@ class EvarAction ( roverlay.packagerules.abstract.actions.PackageRuleAction ):
p_info.update_unsafe ( EVAR=self._evar )
# --- end of apply_action (...) ---
+ def gen_str ( self, level ):
+ yield (
+ level * ' ' + self._evar.name.lower()
+ + ' "' + self._evar.value + '"'
+ )
+ # --- end of gen_str (...) ---
+
# --- end of EvarAction (...) ---
diff --git a/roverlay/packagerules/loader.py b/roverlay/packagerules/loader.py
deleted file mode 100644
index 601264f..0000000
--- a/roverlay/packagerules/loader.py
+++ /dev/null
@@ -1,33 +0,0 @@
-# R overlay -- package rules, package rule loader (from files)
-# -*- coding: utf-8 -*-
-# Copyright (C) 2013 André Erdmann <dywi@mailerd.de>
-# Distributed under the terms of the GNU General Public License;
-# either version 2 of the License, or (at your option) any later version.
-
-__all__ = [ 'PackageRulesLoader', ]
-
-class PackageRulesLoader ( object ):
- """Loads PackageRules from a file."""
-
- # TODO
-
- def __init__ ( self, rules ):
- """Constructor for PackageRulesLoader.
-
- arguments:
- * rules -- object where new rules will be added to
- has to implement an "add_rule()" function
- """
- self.rules = rules
- # --- end of __init__ (...) ---
-
- def load ( self, rule_file ):
- """Loads a rule file.
-
- arguments:
- * rule_file --
- """
- raise NotImplementedError ( "TODO" )
- # --- end of load (...) ---
-
-# --- end of PackageRulesLoader ---
diff --git a/roverlay/packagerules/parser/__init__.py b/roverlay/packagerules/parser/__init__.py
new file mode 100644
index 0000000..a2fcf14
--- /dev/null
+++ b/roverlay/packagerules/parser/__init__.py
@@ -0,0 +1,5 @@
+# R overlay -- package rule parser
+# -*- coding: utf-8 -*-
+# Copyright (C) 2013 André Erdmann <dywi@mailerd.de>
+# Distributed under the terms of the GNU General Public License;
+# either version 2 of the License, or (at your option) any later version.
diff --git a/roverlay/packagerules/parser/context/__init__.py b/roverlay/packagerules/parser/context/__init__.py
new file mode 100644
index 0000000..3a6931e
--- /dev/null
+++ b/roverlay/packagerules/parser/context/__init__.py
@@ -0,0 +1,5 @@
+# R overlay -- package rule parser context
+# -*- coding: utf-8 -*-
+# Copyright (C) 2013 André Erdmann <dywi@mailerd.de>
+# Distributed under the terms of the GNU General Public License;
+# either version 2 of the License, or (at your option) any later version.
diff --git a/roverlay/packagerules/parser/context/action.py b/roverlay/packagerules/parser/context/action.py
new file mode 100644
index 0000000..91f97b6
--- /dev/null
+++ b/roverlay/packagerules/parser/context/action.py
@@ -0,0 +1,90 @@
+# R overlay -- package rule parser, action-block context
+# -*- coding: utf-8 -*-
+# Copyright (C) 2013 André Erdmann <dywi@mailerd.de>
+# Distributed under the terms of the GNU General Public License;
+# either version 2 of the License, or (at your option) any later version.
+
+import roverlay.strutil
+
+import roverlay.packagerules.actions.evar
+import roverlay.packagerules.parser.context.base
+
+class ActionUnknown ( ValueError ):
+ pass
+# --- end of ActionUnknown ---
+
+class ActionNeedsValue ( ValueError ):
+ pass
+# --- end of ActionNeedsValue ---
+
+
+class RuleActionContext (
+ roverlay.packagerules.parser.context.base.BaseContext
+):
+ """RuleActionContext parses action-blocks."""
+
+ # keywords for the "ignore" action
+ KEYWORDS_ACTION_IGNORE = frozenset ((
+ 'ignore',
+ 'do-not-process'
+ ))
+
+ # dict ( <keyword> => <evar class> )
+ # Dict of evar action keywords (with corresponding classes)
+ #
+ KEYWORDS_EVAR = {
+ 'keywords' : roverlay.packagerules.actions.evar.KeywordsEvarAction,
+ }
+
+ def __init__ ( self, namespace ):
+ super ( RuleActionContext, self ).__init__ ( namespace )
+ self._actions = list()
+ # --- end of __init__ (...) ---
+
+ def feed ( self, _str ):
+ """Feeds this action block with input.
+
+ arguments:
+ * _str --
+
+ Raises:
+ * InvalidContext
+ """
+ if _str in self.KEYWORDS_ACTION_IGNORE:
+ if not self._actions:
+ self._actions = None
+ else:
+ raise self.InvalidContext (
+ "ignore action-block does not accept any other action."
+ )
+ elif self._actions is None:
+ raise self.InvalidContext (
+ "ignore action-block does not accept any other action."
+ )
+ else:
+ # split _str into (<keyword>,<value>)
+ argv = roverlay.strutil.split_whitespace ( _str, maxsplit=1 )
+
+ evar_cls = self.KEYWORDS_EVAR.get ( argv [0], None )
+
+ try:
+ if evar_cls:
+ self._actions.append (
+ self.namespace.get_object (
+ evar_cls,
+ roverlay.strutil.unquote ( argv [1] )
+ )
+ )
+ else:
+ raise ActionUnknown ( _str )
+
+ except IndexError:
+ raise ActionNeedsValue ( _str )
+ # --- end of feed (...) ---
+
+ def create ( self ):
+ """Returns all created actions."""
+ return self._actions
+ # --- end of create (...) ---
+
+# --- end of RuleActionContext ---
diff --git a/roverlay/packagerules/parser/context/base.py b/roverlay/packagerules/parser/context/base.py
new file mode 100644
index 0000000..3abb6e8
--- /dev/null
+++ b/roverlay/packagerules/parser/context/base.py
@@ -0,0 +1,51 @@
+# R overlay -- package rule parser, base context
+# -*- coding: utf-8 -*-
+# Copyright (C) 2013 André Erdmann <dywi@mailerd.de>
+# Distributed under the terms of the GNU General Public License;
+# either version 2 of the License, or (at your option) any later version.
+
+class BaseContext ( object ):
+
+ class InvalidContext ( object ):
+ pass
+ # --- end of InvalidContext ---
+
+ def __init__ ( self, namespace ):
+ super ( BaseContext, self ).__init__()
+ self.namespace = namespace
+ # --- end of __init__ (...) ---
+
+ def feed ( self, _str ):
+ raise NotImplementedError()
+ # --- end of feed (...) ---
+
+ def create ( self ):
+ raise NotImplementedError()
+ # --- end of create (...) ---
+
+# --- end of BaseContext ---
+
+
+class NestableContext ( BaseContext ):
+
+ def __init__ ( self, namespace, level=0 ):
+ super ( NestableContext, self ).__init__ ( namespace )
+ self.level = level
+ self._nested = list()
+ # --- end of __init__ (...) ---
+
+ def _new_nested ( self, **kwargs ):
+ o = self.__class__ (
+ namespace = self.namespace,
+ level = self.level + 1,
+ **kwargs
+ )
+ self._nested.append ( o )
+ return o
+ # --- end of _new_nested (...) ---
+
+ def get_nested ( self ):
+ return self._nested [-1]
+ # --- end of get_nested (...) ---
+
+# --- end of NestableContext ---
diff --git a/roverlay/packagerules/parser/context/match.py b/roverlay/packagerules/parser/context/match.py
new file mode 100644
index 0000000..b6e2849
--- /dev/null
+++ b/roverlay/packagerules/parser/context/match.py
@@ -0,0 +1,235 @@
+# R overlay -- package rule parser, match-block context
+# -*- coding: utf-8 -*-
+# Copyright (C) 2013 André Erdmann <dywi@mailerd.de>
+# Distributed under the terms of the GNU General Public License;
+# either version 2 of the License, or (at your option) any later version.
+
+import roverlay.strutil
+
+import roverlay.packagerules.abstract.acceptors
+import roverlay.packagerules.parser.context.base
+import roverlay.packagerules.acceptors.util
+
+from roverlay.packagerules.acceptors import stringmatch
+
+class EndOfMatchContextError ( Exception ):
+ pass
+# --- end of EndOfMatchContextError ---
+
+class MatchDepthError ( Exception ):
+ def __init__ ( self, our_depth, their_depth ):
+ super ( MatchDepthError, self ).__init__ (
+ "{} > {}?".format ( their_depth, our_depth )
+ )
+ # --- end of __init__ (...) ---
+# --- end of MatchDepthError ---
+
+class NoSuchMatchStatement ( ValueError ):
+ def __init__ ( self, statement, reason ):
+ super ( NoSuchMatchStatement, self ).__init__ (
+ "{}: {}".format ( statement, reason )
+ )
+ # --- end of __init__ (...) ---
+# --- end of NoSuchMatchStatement ---
+
+class NoSuchMatchOperator ( ValueError ):
+ pass
+# --- end of NoSuchMatchOperator ---
+
+
+class RuleMatchContext (
+ roverlay.packagerules.parser.context.base.NestableContext
+):
+ """RuleMatchContext parses match-blocks."""
+
+ # chars used to indicate the match depth
+ MATCH_DEPTH_CHARS = "*-"
+
+ # used to set the "boolean type" of a RuleMatchContext, i.e. which
+ # boolean function (acceptor compound class) will be used to combine
+ # all read rules
+ BOOL_AND = 0
+ BOOL_OR = 1
+ BOOL_XOR1 = 2
+ BOOL_NOR = 3
+
+ # dict ( <bool type> => <acceptor compound class> )
+ _BOOL_MAP = {
+ BOOL_AND : roverlay.packagerules.abstract.acceptors.Acceptor_AND,
+ BOOL_OR : roverlay.packagerules.abstract.acceptors.Acceptor_OR,
+ BOOL_XOR1 : roverlay.packagerules.abstract.acceptors.Acceptor_XOR1,
+ BOOL_NOR : roverlay.packagerules.abstract.acceptors.Acceptor_NOR,
+ }
+
+ # operators used for value comparision
+ OP_STRING_EXACT = frozenset (( '==', '=' ))
+ OP_STRING_NOCASE = frozenset (( ',=', '=,' ))
+ OP_REGEX_PARTIAL = frozenset (( '~~', '~' ))
+ OP_REGEX_EXACT = frozenset (( '~=', '=~' ))
+
+ # keywords that introduce a nested match block
+ KEYWORDS_AND = frozenset (( 'and', 'all', '&&' ))
+ KEYWORDS_OR = frozenset (( 'or', 'any', '||' ))
+ KEYWORDS_XOR1 = frozenset (( 'xor1', 'xor', '^^' ))
+ KEYWORDS_NOR = frozenset (( 'nor', 'none' ))
+
+ # dict (
+ # keywords => (
+ # <default acceptor class or None >,
+ # <get_value function>
+ # )
+ # )
+ #
+ # None := ExactRegexAcceptor if {"?","*"} in <string> else StringAcceptor
+ #
+ KEYWORDS_MATCH = {
+ 'repo' : (
+ stringmatch.NocaseStringAcceptor,
+ roverlay.packagerules.acceptors.util.get_repo_name,
+ ),
+ 'repo_name' : (
+ stringmatch.NocaseStringAcceptor,
+ roverlay.packagerules.acceptors.util.get_repo_name,
+ ),
+ 'package' : (
+ None, roverlay.packagerules.acceptors.util.get_package,
+ ),
+ 'package_name' : (
+ None, roverlay.packagerules.acceptors.util.get_package_name,
+ ),
+ 'name' : (
+ None, roverlay.packagerules.acceptors.util.get_package_name,
+ ),
+ }
+
+ def __init__ ( self, namespace, level=0, bool_type=None ):
+ super ( RuleMatchContext, self ).__init__ (
+ namespace = namespace,
+ level = level,
+ )
+ # match statements defined for this instance (nested ones, e.g. ORed,
+ # are in self._nested)
+ self._bool_type = (
+ bool_type if bool_type is not None else self.BOOL_AND
+ )
+ self._matches = list()
+ self._active = True
+ # --- end of __init__ (...) ---
+
+ def _feed ( self, s, match_depth ):
+ assert match_depth >= self.level
+
+ if not self._active:
+ raise EndOfMatchContextError ( "match-block is not active" )
+
+ elif not s:
+ pass
+
+ elif match_depth == self.level:
+ s_low = s.lower()
+
+ if s_low in self.KEYWORDS_AND:
+ self._new_nested ( bool_type=self.BOOL_AND )
+
+ elif s_low in self.KEYWORDS_OR:
+ self._new_nested ( bool_type=self.BOOL_OR )
+
+ elif s_low in self.KEYWORDS_XOR1:
+ self._new_nested ( bool_type=self.BOOL_XOR1 )
+
+ elif s_low in self.KEYWORDS_NOR:
+ self._new_nested ( bool_type=self.BOOL_NOR )
+
+ else:
+ if self._nested:
+ # it's not necessary to mark all (indirect)
+ # child RuleMatchContexts as inactive
+ self.get_nested()._active = False
+
+ argv = roverlay.strutil.split_whitespace ( s )
+ argc = len ( argv )
+
+
+ match_type = self.KEYWORDS_MATCH.get ( argv [0], None )
+
+ if not match_type:
+ raise NoSuchMatchStatement ( argv [0], "unknown" )
+
+ elif argc < 2 or argc > 3:
+ raise NoSuchMatchStatement ( s, "invalid arg count" )
+
+ elif argc == 3:
+ #if argc >= 3:
+ # <keyword> <op> <arg>
+
+ if argv [1] in self.OP_STRING_EXACT:
+ op = stringmatch.StringAcceptor
+ elif argv [1] in self.OP_STRING_NOCASE:
+ op = stringmatch.NocaseStringAcceptor
+ elif argv [1] in self.OP_REGEX_PARTIAL:
+ op = stringmatch.RegexAcceptor
+ elif argv [1] in self.OP_REGEX_EXACT:
+ op = stringmatch.ExactRegexAcceptor
+ else:
+ raise NoSuchMatchOperator ( argv [1] )
+
+ value = argv [2]
+
+ elif argc == 2:
+ # <keyword> <arg>
+ # with op := match_type [0] or <guessed>
+
+ op = match_type [0]
+ value = argv [1]
+
+ if op is None:
+ if '*' in value or '?' in value:
+ op = stringmatch.ExactRegexAcceptor
+ value = roverlay.strutil.wildcard_to_regex ( value, True )
+ else:
+ op = stringmatch.StringAcceptor
+
+ # -- if;
+
+ self._matches.append (
+ self.namespace.get_object (
+ op,
+ 100,
+ match_type [1],
+ value
+ )
+ )
+
+ else:
+ try:
+ return self.get_nested()._feed ( s, match_depth )
+ except IndexError:
+ raise MatchDepthError ( self.level, match_depth )
+ # --- end of _feed (...) ---
+
+ def create ( self ):
+ """Creates and returns an acceptor for this match block."""
+ acceptor = self._BOOL_MAP [self._bool_type] ( priority=100 )
+
+ for match in self._matches:
+ acceptor.add_acceptor ( match )
+
+ for nested in self._nested:
+ acceptor.add_acceptor ( nested.create() )
+
+ return acceptor
+ # --- end of create (...) ---
+
+ def feed ( self, _str ):
+ """Feeds a match block with input.
+
+ arguments:
+ * _str --
+ """
+ # prepare _str for the actual _feed() function
+ # * determine match depth
+ s = _str.lstrip ( self.MATCH_DEPTH_CHARS )
+ return self._feed ( s.lstrip(), len ( _str ) - len ( s ) )
+ # --- end of feed (...) ---
+
+# --- end of RuleMatchContext ---
diff --git a/roverlay/packagerules/parser/context/rule.py b/roverlay/packagerules/parser/context/rule.py
new file mode 100644
index 0000000..dd1a07b
--- /dev/null
+++ b/roverlay/packagerules/parser/context/rule.py
@@ -0,0 +1,201 @@
+# R overlay -- package rule parser, rule context
+# -*- coding: utf-8 -*-
+# Copyright (C) 2013 André Erdmann <dywi@mailerd.de>
+# Distributed under the terms of the GNU General Public License;
+# either version 2 of the License, or (at your option) any later version.
+
+from roverlay.packagerules.abstract import rules
+
+from . import base, match, action
+
+class RuleContext ( base.NestableContext ):
+ """Class for creating rules from text input (feed(<>)) plus using a few
+ control flow functions (end_of_rule(), begin_match(), begin_action()).
+ """
+
+ # CONTEXT_
+ # Used to set/compare the current mode, i.e. how text input will be
+ # interpreted.
+ # * CONTEXT_NONE -- end of the main rule ("self") has been reached
+ # * CONTEXT_MATCH -- set if in a match-block
+ # -> CONTEXT_MAIN_MATCH -- set if in the main match-block
+ # -> CONTEXT_SUB_MATCH -- set if in a nested match-block
+ # * CONTEXT_ACTION -- set if in an action-block
+ # -> CONTEXT_MAIN_ACTION -- set if in the main action-block
+ # -> CONTEXT_SUB_ACTION -- set if in a nested action-block
+ #
+ # * CONTEXT_MAIN -- set if in any main block
+ # * CONTEXT_SUB -- set if in any nested block
+ #
+ # (use bitwise operators to check against these values)
+ #
+ CONTEXT_NONE = 0 # == only
+ CONTEXT_MAIN_MATCH = 1
+ CONTEXT_MAIN_ACTION = 2
+ CONTEXT_SUB_MATCH = 4
+ CONTEXT_SUB_ACTION = 8
+
+ CONTEXT_MATCH = CONTEXT_MAIN_MATCH | CONTEXT_SUB_MATCH
+ CONTEXT_ACTION = CONTEXT_MAIN_ACTION | CONTEXT_SUB_ACTION
+ CONTEXT_MAIN = CONTEXT_MAIN_MATCH | CONTEXT_MAIN_ACTION
+ CONTEXT_SUB = CONTEXT_SUB_MATCH | CONTEXT_SUB_ACTION
+
+ # -- end of CONTEXT_ --
+
+ def __init__ ( self, namespace, level=0 ):
+ super ( RuleContext, self ).__init__ ( namespace, level )
+
+ self.context = self.CONTEXT_MAIN_MATCH
+ self._match_context = match.RuleMatchContext ( self.namespace )
+ self._action_context = action.RuleActionContext ( self.namespace )
+ # --- end of __init__ (...) ---
+
+ def begin_match ( self ):
+ """Create/begin a match-block of a nested rule.
+
+ Raises: InvalidContext,
+ match-blocks are only allowed within an action-block
+ """
+ # nested rules are stored in self._nested (and not in
+ # self._action_context where they syntactically belong to)
+
+ if self.context & self.CONTEXT_MAIN_ACTION:
+ # a nested rule (with depth = 1)
+ self._new_nested()
+ self.context = self.CONTEXT_SUB_MATCH
+ elif self.context & self.CONTEXT_SUB_ACTION:
+ # a nested rule inside a nested one (depth > 1)
+ # => redirect to nested
+ self.get_nested().begin_match()
+ self.context = self.CONTEXT_SUB_MATCH
+ else:
+ # illegal
+ raise self.InvalidContext()
+ # --- end of begin_match (...) ---
+
+ def begin_action ( self ):
+ """Create/begin an action block of a rule (nested or "self").
+
+ Raises: InvalidContext,
+ an action-block has to be preceeded by a match-block
+ """
+ if self.context & self.CONTEXT_MAIN_MATCH:
+ # begin of the main action-block
+ self.context = self.CONTEXT_MAIN_ACTION
+ elif self.context & self.CONTEXT_SUB_MATCH:
+ # action-block of a nested rule
+ # => redirect to nested
+ self.get_nested().begin_action()
+ self.context = self.CONTEXT_SUB_ACTION
+ else:
+ # illegal
+ raise self.InvalidContext()
+ # --- end of begin_action (...) ---
+
+ def end_of_rule ( self ):
+ """Has to be called whenever an end-of-rule statement has been reached
+ and ends a rule, either this one or a nested one (depending on the
+ context).
+
+ Returns True if this rule has been ended, else False (end of a nested
+ rule).
+
+ Raises: InvalidContext,
+ rules can only be closed if within an action-block
+ """
+ if self.context & self.CONTEXT_MAIN_ACTION:
+ # end of this rule
+ self.context = self.CONTEXT_NONE
+ return True
+ elif self.context & self.CONTEXT_SUB_ACTION:
+ if self.get_nested().end_of_rule():
+ # end of child rule (depth=1)
+ self.context = self.CONTEXT_MAIN_ACTION
+
+# no-op, since self.context is already CONTEXT_SUB_ACTION
+# else:
+# # end of a nested rule (depth>1)
+# self.context = self.CONTEXT_SUB_ACTION
+
+ return False
+ else:
+ raise self.InvalidContext()
+ # --- end of end_of_rule (...) ---
+
+ def feed ( self, _str ):
+ """Feed this rule with input (text).
+
+ arguments:
+ * _str --
+
+ Raises: InvalidContext if this rule does not accept input
+ (if self.context is CONTEXT_NONE)
+ """
+ if self.context & self.CONTEXT_MAIN_MATCH:
+ return self._match_context.feed ( _str )
+ elif self.context & self.CONTEXT_MAIN_ACTION:
+ return self._action_context.feed ( _str )
+ elif self.context & self.CONTEXT_SUB:
+ return self.get_nested().feed ( _str )
+ else:
+ raise self.InvalidContext()
+ # --- end of feed (...) ---
+
+ def create ( self ):
+ """Rule 'compilation'.
+
+ Combines all read match- and action-blocks as well as any nested rules
+ into a PackageRule instance (IgnorePackageRule, PackageRule or
+ NestedPackageRule, whatever fits) and returns the result.
+
+ Raises:
+ * Exception if the resulting rule is invalid,
+ e.g. no actions/acceptors defined.
+ * InvalidContext if end_of_rule has not been reached
+ """
+ if self.context != self.CONTEXT_NONE:
+ raise self.InvalidContext ( "end_of_rule not reached." )
+ # -- if;
+
+ package_rule = None
+ actions = self._action_context.create()
+ acceptor = self._match_context.create()
+
+ if not acceptor:
+ raise Exception ( "empty match-block makes no sense." )
+
+ elif len ( self._nested ) > 0:
+ # nested rule
+ if actions is None:
+ raise Exception (
+ "ignore action-block cannot contain nested rules."
+ )
+ else:
+ package_rule = rules.NestedPackageRule()
+ for nested in self._nested:
+ package_rule.add_rule ( nested.create() )
+
+ for action in actions:
+ package_rule.add_action ( action )
+
+ elif actions is None:
+ # ignore rule
+ package_rule = rules.IgnorePackageRule()
+
+ elif actions:
+ # normal rule
+ package_rule = rules.PackageRule()
+
+ for action in actions:
+ package_rule.add_action ( action )
+
+ else:
+ raise Exception ( "empty action-block makes no sense." )
+ # -- if;
+
+ package_rule.set_acceptor ( acceptor )
+
+ return package_rule
+ # --- end of create (...) ---
+
+# --- end of RuleContext ---
diff --git a/roverlay/packagerules/parser/namespace.py b/roverlay/packagerules/parser/namespace.py
new file mode 100644
index 0000000..74dc56f
--- /dev/null
+++ b/roverlay/packagerules/parser/namespace.py
@@ -0,0 +1,65 @@
+# R overlay -- package rule parser, namespace
+# -*- coding: utf-8 -*-
+# Copyright (C) 2013 André Erdmann <dywi@mailerd.de>
+# Distributed under the terms of the GNU General Public License;
+# either version 2 of the License, or (at your option) any later version.
+
+import roverlay.util
+
+class RuleNamespace ( object ):
+ """a RuleNamespace manages RuleParser variables (e.g. objects)."""
+
+ def zap ( self, zap_object_db=False ):
+ if zap_object_db:
+ self._objects.clear()
+ # --- end of zap (...) ---
+
+ def __init__ ( self ):
+ super ( RuleNamespace, self ).__init__()
+
+ # object db
+ # dict (
+ # class => dict (
+ # tuple(hash(args),hash(kwargs)) => instance
+ # )
+ # )
+ #
+ self._objects = dict()
+ # --- end of __init__ (...) ---
+
+ def get_object ( self, cls, *args, **kwargs ):
+ """Returns the desired object.
+
+ The object will be created if it does not already exist in the
+ object db of this namespace.
+
+ !!! The object has to be "shareable", i.e. it must not be modified
+ after constructing it (unless such a side-effect is intentional).
+
+ arguments:
+ * cls --
+ * *args --
+ * **kwargs --
+ """
+ ident = (
+ hash ( args) if args else 0,
+ roverlay.util.get_dict_hash ( kwargs ) if kwargs else 0,
+ )
+
+ objects = self._objects.get ( cls, None )
+
+ if objects is None:
+ c = cls ( *args, **kwargs )
+ self._objects [cls] = { ident : c }
+
+ else:
+ c = objects.get ( ident, None )
+
+ if c is None:
+ c = cls ( *args, **kwargs )
+ objects [ident] = c
+
+ return c
+ # --- end of get_object (...) ---
+
+# --- end of RuleNamespace ---
diff --git a/roverlay/packagerules/parser/text.py b/roverlay/packagerules/parser/text.py
new file mode 100644
index 0000000..968729e
--- /dev/null
+++ b/roverlay/packagerules/parser/text.py
@@ -0,0 +1,110 @@
+# R overlay -- package rule parser, parse text files
+# -*- coding: utf-8 -*-
+# Copyright (C) 2013 André Erdmann <dywi@mailerd.de>
+# Distributed under the terms of the GNU General Public License;
+# either version 2 of the License, or (at your option) any later version.
+
+import roverlay.packagerules.parser.namespace
+import roverlay.packagerules.parser.context.rule
+
+class RuleParser ( object ):
+
+ class NotParseable ( ValueError ):
+ def __init__ ( self, line, lino ):
+ super ( NotParseable, self ).__init__ (
+ "in line {}: cannot parse '{}'.".format ( lino, line )
+ )
+ # --- end of __init__ (...) ---
+ # --- end of NotParseable ---
+
+ # control flow statements
+ # all other keywords are defined in the respective context classes,
+ # namely RuleContext, RuleMatchContext and RuleActionContext
+ KEYWORDS_MATCH = frozenset (( 'match:', 'MATCH:', ))
+ KEYWORDS_ACTION = frozenset (( 'action:', 'ACTION:' ))
+ KEYWORDS_END = frozenset (( 'end;', 'END;' ))
+
+ COMMENT_CHARS = frozenset ( "#;" )
+
+ def _zap ( self ):
+ self.namespace.zap ( zap_object_db=False )
+ # the rule block (RuleContext) that is currently active
+ self._current_rule = None
+ # previous rule blocks
+ self._parsed_rules = list()
+ # --- end of _zap (...) ---
+
+ def __init__ ( self, add_rule_method ):
+ """Constructor for RuleParser.
+
+ arguments:
+ * add_rule_method -- method that will be used to add created rules
+ """
+ super ( RuleParser, self ).__init__()
+ self.namespace = roverlay.packagerules.parser.namespace.RuleNamespace()
+ self.add_rule = add_rule_method
+ self._zap()
+ # --- end of __init__ (...) ---
+
+ def _feed ( self, l, lino ):
+ """Feed this parser with text input.
+
+ arguments:
+ * l -- stripped text line
+ * lino -- line number
+ """
+ if len ( l ) > 0 and l[0] not in self.COMMENT_CHARS:
+ if self._current_rule:
+ if l in self.KEYWORDS_MATCH:
+ self._current_rule.begin_match()
+ elif l in self.KEYWORDS_ACTION:
+ self._current_rule.begin_action()
+ elif l in self.KEYWORDS_END:
+ if self._current_rule.end_of_rule():
+ # add rule to self._parsed_rules
+ self._parsed_rules.append ( self._current_rule )
+ self._current_rule = None
+ # else end of a nested rule, do nothing
+ else:
+ self._current_rule.feed ( l )
+
+ elif l in self.KEYWORDS_MATCH:
+ self._current_rule = (
+ roverlay.packagerules.parser.context.rule.RuleContext (
+ self.namespace
+ )
+ )
+
+ else:
+ raise NotParseable ( l, lino )
+ # --- end of _feed (...) ---
+
+ def _create ( self ):
+ """Generator that yields package rules."""
+ if self._current_rule:
+ raise Exception ( "end_of_rule not reached." )
+
+ for c in self._parsed_rules:
+ yield c.create()
+ # --- end of _create (...) --
+
+ def load ( self, rule_file ):
+ """Loads a rule file and adds the created rules using the add_rule
+ method that has been given at initialization time (see __init__()).
+
+ Returns: None (implicit)
+
+ arguments:
+ * rule_file --
+ """
+ self._zap()
+
+ with open ( rule_file, 'r' ) as FH:
+ for lino, line in enumerate ( FH.readlines() ):
+ self._feed ( line.strip(), lino )
+
+ for rule in self._create():
+ self.add_rule ( rule )
+ # --- end of load (...) ---
+
+# --- end of RuleParser ---
diff --git a/roverlay/packagerules/rules.py b/roverlay/packagerules/rules.py
index 1c1adbc..a75bbbb 100644
--- a/roverlay/packagerules/rules.py
+++ b/roverlay/packagerules/rules.py
@@ -6,10 +6,13 @@
__all__ = [ 'PackageRules', ]
-import roverlay.packagerules.abstract.rules
-import roverlay.packagerules.loader
+import logging
+
+import roverlay.config
+import roverlay.util
-#import roverlay.packagerules.actions.evar
+import roverlay.packagerules.abstract.rules
+import roverlay.packagerules.parser.text
class PackageRules ( roverlay.packagerules.abstract.rules.NestedPackageRule ):
"""The top level rule.
@@ -27,28 +30,53 @@ class PackageRules ( roverlay.packagerules.abstract.rules.NestedPackageRule ):
This is a stub since package rule loading is not implemented.
"""
rules = PackageRules()
- f = rules.get_loader()
- # "example usage" (just a reminder for PackageRulesLoader)
-# rules.add_action (
-# roverlay.packagerules.actions.evar.KeywordsEvarAction ( "amd64" )
-# )
+
+ flist = roverlay.config.get ( 'package_rules.files', False )
+ if flist:
+ loader = rules.get_parser()
+ roverlay.util.for_all_files ( flist, loader.load )
+
+ rules.prepare()
+
return rules
# --- end of get_configured (...) ---
def __init__ ( self ):
super ( PackageRules, self ).__init__ ( priority=-1 )
+ del self._acceptor
+ self.logger = logging.getLogger ( self.__class__.__name__ )
+ self.is_toplevel = True
# --- end of __init__ (...) ---
- def get_loader ( self ):
- """Returns a PackageRulesLoader that reads files and adds rules to
- this PackageRules instance.
+ def prepare ( self ):
+ super ( PackageRules, self ).prepare()
+ self.set_logger ( self.logger )
+ # --- end of prepare (...) ---
+
+ def get_parser ( self ):
+ """Returns a RuleParser that reads package rules from text files
+ and adds them to this PackageRules instance.
+
+ Note that prepare() has to be called after loading rules.
"""
- return roverlay.packagerules.loader.PackageRulesLoader ( self )
- # --- end of get_loader (...) ---
+ return roverlay.packagerules.parser.text.RuleParser ( self.add_rule )
+ # --- end of get_parser (...) ---
def accepts ( self, p_info ):
"""Returns True (and therefore doesn't need to be called)."""
return True
# --- end of accepts (...) ---
+ def __str__ ( self ):
+ """Exports all rules to text in rule file syntax.
+
+ Note:
+ Due to the "lenient" syntax, the output is not necessarily identical
+ to what has been read with get_parser().
+ """
+ return '\n'.join (
+ self._gen_rules_str ( level=0 )
+ )
+ # --- end of __str__ (...) ---
+
# --- end of PackageRules ---
diff --git a/setup.py b/setup.py
index d8c3e6d..eae7393 100755
--- a/setup.py
+++ b/setup.py
@@ -26,10 +26,10 @@ core.setup (
'roverlay/overlay/pkgdir/symlink',
'roverlay/packagerules',
'roverlay/packagerules/abstract',
-# 'roverlay/packagerules/acceptors',
+ 'roverlay/packagerules/acceptors',
'roverlay/packagerules/actions',
-# 'roverlay/packagerules/parser',
-# 'roverlay/packagerules/parser/context',
+ 'roverlay/packagerules/parser',
+ 'roverlay/packagerules/parser/context',
'roverlay/recipe',
'roverlay/remote',
'roverlay/rpackage',
^ permalink raw reply related [flat|nested] only message in thread
only message in thread, other threads:[~2013-02-05 17:48 UTC | newest]
Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2013-02-05 17:48 [gentoo-commits] proj/R_overlay:master commit in: /, roverlay/packagerules/parser/context/, roverlay/packagerules/parser/, André Erdmann
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox