Repository: hgrecco/pint

Issue:
How to use sign %?
## Define Sign %

I define:
`from pint import UnitRegistry`
`ureg=UnitRegistry()`
`ureg.define('percent = 1 = %')`
`x = 20 * u('1 %')`
`print('x: {0}'.format(x))`
`print('x: {:L}'.format(x))`

and the output is:
`x: 20`
ValueError: Unknown format code 'L' for object of type 'int'

I need:
`x: 20%`
`x: 20\%`


Discussion:

Patch:
diff --git a/pint/registry.py b/pint/registry.py
index 4324c7d..23b1108 100644
--- a/pint/registry.py
+++ b/pint/registry.py
@@ -101,6 +101,8 @@ class BaseRegistry(meta.with_metaclass(_Meta)):
                             'warn', 'raise', 'ignore'
     :type on_redefinition: str
     :param auto_reduce_dimensions: If True, reduce dimensionality on appropriate operations.
+    :param preprocessors: list of callables which are iteratively ran on any input expression
+                          or unit string
     """
 
     #: Map context prefix to function
@@ -116,13 +118,15 @@ class BaseRegistry(meta.with_metaclass(_Meta)):
             'parse_unit_name', 'parse_units', 'parse_expression',
             'convert']
 
-    def __init__(self, filename='', force_ndarray=False, on_redefinition='warn', auto_reduce_dimensions=False):
+    def __init__(self, filename='', force_ndarray=False, on_redefinition='warn',
+                 auto_reduce_dimensions=False, preprocessors=None):
 
         self._register_parsers()
         self._init_dynamic_classes()
 
         self._filename = filename
         self.force_ndarray = force_ndarray
+        self.preprocessors = preprocessors or []
 
         #: Action to take in case a unit is redefined. 'warn', 'raise', 'ignore'
         self._on_redefinition = on_redefinition
@@ -813,6 +817,8 @@ class BaseRegistry(meta.with_metaclass(_Meta)):
             :class:`pint.UndefinedUnitError` if a unit is not in the registry
             :class:`ValueError` if the expression is invalid.
         """
+        for p in self.preprocessors:
+            input_string = p(input_string)
         units = self._parse_units(input_string, as_delta)
         return self.Unit(units)
 
@@ -881,6 +887,8 @@ class BaseRegistry(meta.with_metaclass(_Meta)):
         if not input_string:
             return self.Quantity(1)
 
+        for p in self.preprocessors:
+            input_string = p(input_string)
         input_string = string_preprocessor(input_string)
         gen = tokenizer(input_string)
 
@@ -1514,19 +1522,22 @@ class UnitRegistry(SystemRegistry, ContextRegistry, NonMultiplicativeRegistry):
                             'warn', 'raise', 'ignore'
     :type on_redefinition: str
     :param auto_reduce_dimensions: If True, reduce dimensionality on appropriate operations.
+    :param preprocessors: list of callables which are iteratively ran on any input expression
+                          or unit string
     """
 
     def __init__(self, filename='', force_ndarray=False, default_as_delta=True,
                  autoconvert_offset_to_baseunit=False,
                  on_redefinition='warn', system=None,
-                 auto_reduce_dimensions=False):
+                 auto_reduce_dimensions=False, preprocessors=None):
 
         super(UnitRegistry, self).__init__(filename=filename, force_ndarray=force_ndarray,
                                            on_redefinition=on_redefinition,
                                            default_as_delta=default_as_delta,
                                            autoconvert_offset_to_baseunit=autoconvert_offset_to_baseunit,
                                            system=system,
-                                           auto_reduce_dimensions=auto_reduce_dimensions)
+                                           auto_reduce_dimensions=auto_reduce_dimensions,
+                                           preprocessors=preprocessors)
 
     def pi_theorem(self, quantities):
         """Builds dimensionless quantities using the Buckingham π theorem


Test patch:
diff --git a/pint/testsuite/test_unit.py b/pint/testsuite/test_unit.py
index 9c6bf51..0879627 100644
--- a/pint/testsuite/test_unit.py
+++ b/pint/testsuite/test_unit.py
@@ -3,7 +3,9 @@
 from __future__ import division, unicode_literals, print_function, absolute_import
 
 import copy
+import functools
 import math
+import re
 
 from pint.registry import (UnitRegistry, LazyRegistry)
 from pint.util import (UnitsContainer, ParserHelper)
@@ -274,6 +276,28 @@ class TestRegistry(QuantityTestCase):
         self.assertEqual(parse('kelvin*meter', as_delta=True), UnitsContainer(kelvin=1, meter=1))
         self.assertEqual(parse('kelvin*meter', as_delta=False), UnitsContainer(kelvin=1, meter=1))
 
+    def test_parse_expression_with_preprocessor(self):
+        # Add parsing of UDUNITS-style power
+        self.ureg.preprocessors.append(functools.partial(
+            re.sub, r'(?<=[A-Za-z])(?![A-Za-z])(?<![0-9\-][eE])(?<![0-9\-])(?=[0-9\-])', '**'))
+        # Test equality
+        self.assertEqual(self.ureg.parse_expression('42 m2'), self.Q_(42, UnitsContainer(meter=2.)))
+        self.assertEqual(self.ureg.parse_expression('1e6 Hz s-2'), self.Q_(1e6, UnitsContainer(second=-3.)))
+        self.assertEqual(self.ureg.parse_expression('3 metre3'), self.Q_(3, UnitsContainer(meter=3.)))
+        # Clean up and test previously expected value
+        self.ureg.preprocessors.pop()
+        self.assertEqual(self.ureg.parse_expression('1e6 Hz s-2'), self.Q_(999998., UnitsContainer()))
+
+    def test_parse_unit_with_preprocessor(self):
+        # Add parsing of UDUNITS-style power
+        self.ureg.preprocessors.append(functools.partial(
+            re.sub, r'(?<=[A-Za-z])(?![A-Za-z])(?<![0-9\-][eE])(?<![0-9\-])(?=[0-9\-])', '**'))
+        # Test equality
+        self.assertEqual(self.ureg.parse_units('m2'), UnitsContainer(meter=2.))
+        self.assertEqual(self.ureg.parse_units('m-2'), UnitsContainer(meter=-2.))
+        # Clean up
+        self.ureg.preprocessors.pop()
+
     def test_name(self):
         self.assertRaises(UndefinedUnitError, self.ureg.get_name, 'asdf')