public inbox for gentoo-commits@lists.gentoo.org
 help / color / mirror / Atom feed
* [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