import shlex
import re
from fractions import Fraction

from .exceptions import ParseError

quantity_rx = re.compile(r'^(((?P<ipart>\d+) )?(?P<fpart>\d+/\d+)|(?P<amount>\d+([.]\d+)?))( (?P<unit>.*))?$')

def _to_number(amount):
  try:
    return int(amount)
  except ValueError:
    pass
  try:
    return float(amount)
  except ValueError:
    pass
  try:
    return Fraction(amount)
  except ValueError:
    pass
  raise ParseError("Can't convert to number", amount)

class Quantity(object):
  """ Class for representing quantity.

  Quantities can either be a measurement (like ``1 cup``) or a dimensionless
  amount (like ``12``).

  :ivar amount: numeric amount of the quantity
  :type amount: int or float or fractions.Fraction
  :ivar str unit: unit of the amount, if any
  """
  amount = None 
  unit = None

  @classmethod
  def parse(cls, s):
    """
    Parse an object from a string. Valid strings are of the form:

    ::

      amount[ unit] 
    
    Where ``unit`` is unconstrained and ``amount`` is one of the following:

    * an integer, like ``4``
    * a decimal number, like ``4.5`` (*not* scientific notation)
    * a fraction, like ``1/2``
    * a mixed number, like ``1 1/2``

    :param str s: string to parse
    :raises ParseError: on invalid syntax
    :returns: the resulting Quantity 
    :rtype: Quantity 
    """
    m = quantity_rx.match(s)
    if not m:
      raise ParseError("Not valid quantity syntax", s)
    amount, unit, ipart, fpart = m.group('amount', 'unit', 'ipart', 'fpart')
    if amount:
      amount = _to_number(amount)
    elif ipart and not fpart:
      amount = int(ipart)
    else:
      assert fpart
      amount = int(ipart or 0) + Fraction(fpart)
    return cls(amount=amount, unit=unit)
  
  def __init__(self, amount=None, unit=None):
    self.amount = amount
    self.unit = unit

  def __mul__(self, factor):
    """ Scale amount by a factor. 

    Note: not unit safe. ``factor`` must be dimensionless.
    """
    cls = type(self)
    return cls(factor * self.amount,
               self.unit)

  def __rmul__(self, factor):
    return self * factor

  def __eq__(self, other):
    if type(self) != type(other):
      return False
    return (self.unit == other.unit and
            self.amount == other.amount)

  def __str__(self):
    def strfrac(s):
      if not isinstance(s, Fraction):
        return s
      ipart = int(s.numerator/s.denominator)
      fpart = s - ipart
      if ipart and fpart:
        return "{} {}".format(ipart, fpart)
      return str(ipart or fpart or 0)
    s = strfrac(self.amount)
    if self.unit is None:
      return str(s)
    else:
      return "{} {}".format(s, self.unit)

  def __repr__(self):
    return "<Quantity: {}>".format(str(self))

class QuantityDescriptor(object):

  def __init__(self, value=None, convert=False):
    self.convert = convert
    self.default = self._check(value)

  def __get__(self, obj, objtype):
    return obj.__dict__.get(self, self.default)

  def __set__(self, obj, value):
    obj.__dict__[self] = self._check(value)

  def _check(self, value):
    if isinstance(value, basestring) and self.convert:
      value = Quantity.parse(value)
    elif value is None:
      pass
    elif not isinstance(value, Quantity):
      raise TypeError("Not a Quantity", value)
    return value

@@ -84,24 +84,22 @@
 eError%0A%0A
-quantity
+number
 _rx = re
@@ -177,112 +177,214 @@
 )?))
-( (?P%3Cunit%3E.*))?$')%0A%0Adef _to_number(amount):%0A  try:%0A    return int(amount)%0A  except ValueError:%0A    pass
+$')%0Aquantity_rx = re.compile(r'%5E(?P%3Cnumber%3E%5B0-9./%5D+( %5B0-9./%5D+)?)( (?P%3Cunit%3E.*))?$')%0A%0Adef _to_number(amount):%0A  m = number_rx.match(amount)%0A  if not m:%0A    raise ValueError(%22Can't convert to number%22, amount)
 %0A  t
@@ -398,20 +398,18 @@
  return 
-floa
+in
 t(amount
@@ -458,24 +458,21 @@
  return 
-Fraction
+float
 (amount)
@@ -506,61 +506,151 @@
 ass%0A
+%0A
   
-raise ParseError(%22Can't convert to number%22, amount)
+ipart, fpart = m.group('ipart', 'fpart')%0A  value = int(ipart) if ipart else 0%0A  if fpart:%0A    value = value + Fraction(fpart)%0A  return value
 %0A%0Acl
@@ -1629,33 +1629,19 @@
     
-amount, unit, ipart, fpar
+number, uni
 t = 
@@ -1649,22 +1649,22 @@
 .group('
-amount
+number
 ', 'unit
@@ -1668,41 +1668,17 @@
 nit'
-, 'ipart', 'fpart')%0A    if amount
+)%0A    try
 :%0A  
@@ -1705,148 +1705,90 @@
 ber(
-amount
+number
 )%0A    e
-lif ipart and not fpart:%0A      amount = int(ipart)%0A    else:%0A      assert fpart%0A      amount = int(ipart or 0) + Fraction(fpart
+xcept ValueError:%0A      raise ParseError(%22Not a valid number%22, number
 )%0A  

make quantity._to_number handle mixed numbers