summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHernan Grecco <hernan.grecco@gmail.com>2014-03-02 03:53:45 -0300
committerHernan Grecco <hernan.grecco@gmail.com>2014-03-02 03:53:45 -0300
commitd9ace7996374cfe0bfd23ae122c34ab8fcea2196 (patch)
treeafd05993b4439551e990a23b2ec8baa070f32a4f
parent72ff82545a89f62dfdb7114abd71decc4dd01384 (diff)
parent96bcb223421581c85ee36906ca450b3b7fb8e2c9 (diff)
downloadpint-d9ace7996374cfe0bfd23ae122c34ab8fcea2196.tar.gz
Fixed conflicts with rec-issue-107 and merged
-rw-r--r--CHANGES6
-rw-r--r--docs/faq.rst2
-rw-r--r--pint/__init__.py6
-rw-r--r--pint/formatter.py182
-rw-r--r--pint/quantity.py5
-rw-r--r--pint/testsuite/__init__.py10
-rw-r--r--pint/testsuite/test_quantity.py4
-rw-r--r--pint/unit.py34
-rw-r--r--pint/util.py71
9 files changed, 215 insertions, 105 deletions
diff --git a/CHANGES b/CHANGES
index dec9828..3e8ce79 100644
--- a/CHANGES
+++ b/CHANGES
@@ -5,6 +5,12 @@ Pint Changelog
0.5 (unreleased)
----------------
+- Nothing changed yet.
+
+
+0.4.2 (2014-02-14)
+------------------
+
- Python 2.6 support
(Issue #96, thanks tiagocoutinho)
- Fixed symbol for inch.
diff --git a/docs/faq.rst b/docs/faq.rst
index f163de4..08bfe74 100644
--- a/docs/faq.rst
+++ b/docs/faq.rst
@@ -25,4 +25,6 @@ You mention other similar Python libraries. Can you point me to those?
`Units <https://bitbucket.org/adonohue/units/>`_
+`udunitspy <https://github.com/blazetopher/udunitspy>`_
+
If your are aware of another one, please let me know.
diff --git a/pint/__init__.py b/pint/__init__.py
index d91d9c1..049a7d6 100644
--- a/pint/__init__.py
+++ b/pint/__init__.py
@@ -15,8 +15,10 @@ from __future__ import with_statement
import os
import subprocess
import pkg_resources
+from .formatter import formatter
from .unit import UnitRegistry, DimensionalityError, UndefinedUnitError
-from .util import formatter, pi_theorem, logger
+from .util import pi_theorem, logger
+
from .context import Context
_DEFAULT_REGISTRY = UnitRegistry()
@@ -31,7 +33,7 @@ except: # on any error just try to grab the version that is installed on the sy
__version__ = pkg_resources.get_distribution('pint').version
except:
pass # we seem to have a local copy without any repository control or installed without setuptools
- # so the reported version will be __unknown__
+ # so the reported version will be __unknown__
def _build_quantity(value, units):
return _DEFAULT_REGISTRY.Quantity(value, units)
diff --git a/pint/formatter.py b/pint/formatter.py
new file mode 100644
index 0000000..859a4d9
--- /dev/null
+++ b/pint/formatter.py
@@ -0,0 +1,182 @@
+# -*- coding: utf-8 -*-
+"""
+ pint.formatter
+ ~~~~~~~~~
+
+ Format units for pint.
+
+ :copyright: 2013 by Pint Authors, see AUTHORS for more details.
+ :license: BSD, see LICENSE for more details.
+"""
+
+from __future__ import division, unicode_literals, print_function, absolute_import
+
+import re
+
+__JOIN_REG_EXP = re.compile("\{\d*\}")
+
+def _join(fmt, iterable):
+ if not iter:
+ return ''
+ if not __JOIN_REG_EXP.search(fmt):
+ return fmt.join(iterable)
+ miter = iter(iterable)
+ first = next(miter)
+ for val in miter:
+ ret = fmt.format(first, val)
+ first = ret
+ return first
+
+_PRETTY_EXPONENTS = '⁰¹²³⁴⁵⁶⁷⁸⁹'
+
+def _pretty_fmt_exponent(num):
+ ret = '{0:n}'.format(num).replace('-', '⁻')
+ for n in range(10):
+ ret = ret.replace(str(n), _PRETTY_EXPONENTS[n])
+ return ret
+
+# _FORMATS maps format specifications to the corresponding argument set to
+# formatter().
+_FORMATS = {
+ 'P': { # Pretty format.
+ 'as_ratio': True,
+ 'single_denominator': False,
+ 'product_fmt': '·',
+ 'division_fmt': '/',
+ 'power_fmt': '{0}{1}',
+ 'parentheses_fmt': '({0})',
+ 'exp_call': _pretty_fmt_exponent,
+ },
+
+ 'L': { # Latex print format.
+ 'as_ratio': True,
+ 'single_denominator': True,
+ 'product_fmt': r' \cdot ',
+ 'division_fmt': r'\frac[{0}][{1}]',
+ 'power_fmt': '{0}^[{1}]',
+ 'parentheses_fmt': '{0}^[{1}]',
+ },
+
+ 'H': { # Latex format.
+ 'as_ratio': True,
+ 'single_denominator': True,
+ 'product_fmt': r' ',
+ 'division_fmt': r'{0}/{1}',
+ 'power_fmt': '{0}<sup>{1}</sup>',
+ 'parentheses_fmt': r'({0})',
+ },
+
+ '': { # Default format.
+ 'as_ratio': True,
+ 'single_denominator': False,
+ 'product_fmt': ' * ',
+ 'division_fmt': ' / ',
+ 'power_fmt': '{0} ** {1}',
+ 'parentheses_fmt': r'({0})',
+ },
+
+ 'C': { # Compact format.
+ 'as_ratio': True,
+ 'single_denominator': False,
+ 'product_fmt': '*', # TODO: Should this just be ''?
+ 'division_fmt': '/',
+ 'power_fmt': '{0}**{1}',
+ 'parentheses_fmt': r'({0})',
+ },
+ }
+
+def formatter(items, as_ratio=True, single_denominator=False,
+ product_fmt=' * ', division_fmt=' / ', power_fmt='{0} ** {1}',
+ parentheses_fmt='({0})', exp_call=lambda x: '{0:n}'.format(x)):
+ """Format a list of (name, exponent) pairs.
+
+ :param items: a list of (name, exponent) pairs.
+ :param as_ratio: True to display as ratio, False as negative powers.
+ :param single_denominator: all with terms with negative exponents are
+ collected together.
+ :param product_fmt: the format used for multiplication.
+ :param division_fmt: the format used for division.
+ :param power_fmt: the format used for exponentiation.
+ :param parentheses_fmt: the format used for parenthesis.
+
+ :return: the formula as a string.
+ """
+ if as_ratio:
+ fun = lambda x: exp_call(abs(x))
+ else:
+ fun = exp_call
+
+ pos_terms, neg_terms = [], []
+
+ for key, value in sorted(items):
+ if value == 1:
+ pos_terms.append(key)
+ elif value > 0:
+ pos_terms.append(power_fmt.format(key, fun(value)))
+ elif value == -1:
+ neg_terms.append(key)
+ else:
+ neg_terms.append(power_fmt.format(key, fun(value)))
+
+ if pos_terms:
+ pos_ret = _join(product_fmt, pos_terms)
+ elif as_ratio and neg_terms:
+ pos_ret = '1'
+ else:
+ pos_ret = ''
+
+ if not neg_terms:
+ return pos_ret
+
+ if as_ratio:
+ if single_denominator:
+ neg_ret = _join(product_fmt, neg_terms)
+ if len(neg_terms) > 1:
+ neg_ret = parentheses_fmt.format(neg_ret)
+ else:
+ neg_ret = _join(division_fmt, neg_terms)
+ else:
+ neg_ret = product_fmt.join(neg_terms)
+
+ return _join(division_fmt, [pos_ret, neg_ret])
+
+# Extract just the type from the specification mini-langage: see
+# http://docs.python.org/2/library/string.html#format-specification-mini-language
+
+_BASIC_TYPES = frozenset('bcdeEfFgGnosxX%')
+_KNOWN_TYPES = frozenset(_FORMATS.keys())
+
+def _parse_spec(spec):
+ result = ''
+ for ch in reversed(spec):
+ if ch == '~' or ch in _BASIC_TYPES:
+ continue
+ elif ch in _KNOWN_TYPES:
+ if result:
+ raise ValueError("expected ':' after format specifier")
+ else:
+ result = ch
+ elif ch.isalpha():
+ raise ValueError("Unknown conversion specified " + ch)
+ else:
+ break
+ return result
+
+def format_unit(unit, spec):
+ if not unit:
+ return 'dimensionless'
+ spec = _parse_spec(spec)
+ fmt = _FORMATS.get(spec)
+ if not fmt:
+ raise ValueError('Unknown conversion specifier ' + spec)
+
+ result = formatter(unit.items(), **fmt)
+ if spec == 'L':
+ result = result.replace('[', '{').replace(']', '}')
+ return result
+
+def remove_custom_flags(spec):
+ for flag in _FORMATS.keys():
+ if flag:
+ spec = spec.replace(flag, '')
+ return spec
diff --git a/pint/quantity.py b/pint/quantity.py
index f42b756..924f74b 100644
--- a/pint/quantity.py
+++ b/pint/quantity.py
@@ -14,6 +14,7 @@ import operator
import functools
from collections import Iterable
+from .formatter import remove_custom_flags
from .unit import DimensionalityError, UnitsContainer, UnitDefinition, UndefinedUnitError
from .compat import string_types, ndarray, np, _to_magnitude
@@ -144,8 +145,8 @@ class _Quantity(object):
else:
units = self.units
- return format(self.magnitude, spec.replace('L', '').replace('P', '').replace('H', '')) \
- + ' ' + format(units, spec)
+ return '%s %s' % (format(self.magnitude, remove_custom_flags(spec)),
+ format(units, spec))
# IPython related code
def _repr_html_(self):
diff --git a/pint/testsuite/__init__.py b/pint/testsuite/__init__.py
index 2710797..528ead7 100644
--- a/pint/testsuite/__init__.py
+++ b/pint/testsuite/__init__.py
@@ -75,8 +75,9 @@ class TestCase(unittest.TestCase):
val = abs((second - first) / (second + first))
self.assertLess(val, rel, msg=msg)
+
def testsuite():
- """A testsuite that has all the pyflim tests.
+ """A testsuite that has all the pint tests.
"""
return unittest.TestLoader().discover(os.path.dirname(__file__))
@@ -87,3 +88,10 @@ def main():
unittest.main()
except Exception as e:
print('Error: %s' % e)
+
+
+def run():
+ """Run all tests i
+ """
+ test_runner = unittest.TextTestRunner()
+ test_runner.run(testsuite())
diff --git a/pint/testsuite/test_quantity.py b/pint/testsuite/test_quantity.py
index d948cad..088ea4f 100644
--- a/pint/testsuite/test_quantity.py
+++ b/pint/testsuite/test_quantity.py
@@ -133,10 +133,12 @@ class TestQuantity(TestCase):
('{0:L}', r'4.12345678 \frac{kilogram \cdot meter^{2}}{second}'),
('{0:P}', '4.12345678 kilogram·meter²/second'),
('{0:H}', '4.12345678 kilogram meter<sup>2</sup>/second'),
+ ('{0:C}', '4.12345678 kilogram*meter**2/second'),
('{0:~}', '4.12345678 kg * m ** 2 / s'),
('{0:L~}', r'4.12345678 \frac{kg \cdot m^{2}}{s}'),
('{0:P~}', '4.12345678 kg·m²/s'),
('{0:H~}', '4.12345678 kg m<sup>2</sup>/s'),
+ ('{0:C~}', '4.12345678 kg*m**2/s'),
):
self.assertEqual(spec.format(x), result)
@@ -146,10 +148,12 @@ class TestQuantity(TestCase):
for spec, result in (('L', r'4.12345678 \frac{kilogram \cdot meter^{2}}{second}'),
('P', '4.12345678 kilogram·meter²/second'),
('H', '4.12345678 kilogram meter<sup>2</sup>/second'),
+ ('C', '4.12345678 kilogram*meter**2/second'),
('~', '4.12345678 kg * m ** 2 / s'),
('L~', r'4.12345678 \frac{kg \cdot m^{2}}{s}'),
('P~', '4.12345678 kg·m²/s'),
('H~', '4.12345678 kg m<sup>2</sup>/s'),
+ ('C~', '4.12345678 kg*m**2/s'),
):
ureg.default_format = spec
self.assertEqual('{0}'.format(x), result)
diff --git a/pint/unit.py b/pint/unit.py
index d45209a..c4fa928 100644
--- a/pint/unit.py
+++ b/pint/unit.py
@@ -24,9 +24,10 @@ from numbers import Number
from tokenize import untokenize, NUMBER, STRING, NAME, OP
from .context import Context, ContextChain, _freeze
-from .util import (formatter, logger, pi_theorem, solve_dependencies, ParserHelper,
+from .util import (logger, pi_theorem, solve_dependencies, ParserHelper,
string_preprocessor, find_connected_nodes, find_shortest_path)
from .compat import tokenizer, string_types, NUMERIC_TYPES, TransformDict
+from .formatter import format_unit
class UndefinedUnitError(ValueError):
@@ -298,39 +299,14 @@ class UnitsContainer(dict):
return dict.__eq__(self, other)
def __str__(self):
- if not self:
- return 'dimensionless'
- return formatter(self.items())
+ return self.__format__('')
def __repr__(self):
tmp = '{%s}' % ', '.join(["'{0}': {1}".format(key, value) for key, value in sorted(self.items())])
return '<UnitsContainer({0})>'.format(tmp)
def __format__(self, spec):
- if 'L' in spec:
- tmp = formatter(self.items(), True, True,
- r' \cdot ', r'\frac[{0}][{1}]', '{0}^[{1}]',
- r'\left( {0} \right)')
- tmp = tmp.replace('[', '{').replace(']', '}')
- return tmp
- elif 'P' in spec:
- def fmt_exponent(num):
- PRETTY = '⁰¹²³⁴⁵⁶⁷⁸⁹'
- ret = '{0:n}'.format(num).replace('-', '⁻')
- for n in range(10):
- ret = ret.replace(str(n), PRETTY[n])
- return ret
- tmp = formatter(self.items(), True, False,
- '·', '/', '{0}{1}',
- '({0})', fmt_exponent)
- return tmp
- elif 'H' in spec:
- tmp = formatter(self.items(), True, True,
- r' ', r'{0}/{1}', '{0}<sup>{1}</sup>',
- r'({0})')
- return tmp
- else:
- return str(self)
+ return format_unit(self, spec)
def __copy__(self):
ret = self.__class__()
@@ -956,7 +932,7 @@ class UnitRegistry(object):
# must first convert to Decimal before we can '*' the values
if isinstance(value, Decimal):
return Decimal(str(factor)) * value
-
+
return factor * value
def pi_theorem(self, quantities):
diff --git a/pint/util.py b/pint/util.py
index 1730006..f8ffd15 100644
--- a/pint/util.py
+++ b/pint/util.py
@@ -113,77 +113,6 @@ def column_echelon_form(matrix, ntype=Fraction, transpose_result=False):
return _transpose(M), _transpose(I), swapped
-__JOIN_REG_EXP = re.compile("\{\d*\}")
-
-def _join(fmt, iterable):
- if not iter:
- return ''
- if not __JOIN_REG_EXP.search(fmt):
- return fmt.join(iterable)
- miter = iter(iterable)
- first = next(miter)
- for val in miter:
- ret = fmt.format(first, val)
- first = ret
- return first
-
-
-def formatter(items, as_ratio=True, single_denominator=False,
- product_fmt=' * ', division_fmt=' / ', power_fmt='{0} ** {1}',
- parentheses_fmt='({0})', exp_call=lambda x: '{0:n}'.format(x)):
- """Format a list of (name, exponent) pairs.
-
- :param items: a list of (name, exponent) pairs.
- :param as_ratio: True to display as ratio, False as negative powers.
- :param single_denominator: all with terms with negative exponents are
- collected together.
- :param product_fmt: the format used for multiplication.
- :param division_fmt: the format used for division.
- :param power_fmt: the format used for exponentiation.
- :param parentheses_fmt: the format used for parenthesis.
-
- :return: the formula as a string.
- """
- if as_ratio:
- fun = lambda x: exp_call(abs(x))
- else:
- fun = exp_call
-
- pos_terms, neg_terms = [], []
-
- for key, value in sorted(items):
- if value == 1:
- pos_terms.append(key)
- elif value > 0:
- pos_terms.append(power_fmt.format(key, fun(value)))
- elif value == -1:
- neg_terms.append(key)
- else:
- neg_terms.append(power_fmt.format(key, fun(value)))
-
- if pos_terms:
- pos_ret = _join(product_fmt, pos_terms)
- elif as_ratio and neg_terms:
- pos_ret = '1'
- else:
- pos_ret = ''
-
- if not neg_terms:
- return pos_ret
-
- if as_ratio:
- if single_denominator:
- neg_ret = _join(product_fmt, neg_terms)
- if len(neg_terms) > 1:
- neg_ret = parentheses_fmt.format(neg_ret)
- else:
- neg_ret = _join(division_fmt, neg_terms)
- else:
- neg_ret = product_fmt.join(neg_terms)
-
- return _join(division_fmt, [pos_ret, neg_ret])
-
-
def pi_theorem(quantities, registry=None):
"""Builds dimensionless quantities using the Buckingham π theorem