Commit b2da162e by Peter Baratta

Add docstrings to preview.py and test_preview.py

parent 88ae2f11
...@@ -62,6 +62,9 @@ class LatexRendered(object): ...@@ -62,6 +62,9 @@ class LatexRendered(object):
def render_number(children): def render_number(children):
"""
Combine the elements forming the number, escaping the suffix if needed.
"""
# TODO exponential notation # TODO exponential notation
if children[-1].latex in SUFFIXES: if children[-1].latex in SUFFIXES:
children[-1].latex = ur"\text{{{suffix}}}".format(suffix=children[-1].latex) children[-1].latex = ur"\text{{{suffix}}}".format(suffix=children[-1].latex)
...@@ -69,7 +72,13 @@ def render_number(children): ...@@ -69,7 +72,13 @@ def render_number(children):
def variable_closure(variables, casify): def variable_closure(variables, casify):
"""
Wrap `render_variable` so it knows the variables allowed.
"""
def render_variable(children): def render_variable(children):
"""
Replace greek letters, otherwise escape the variable names.
"""
# TODO epsilon_0 # TODO epsilon_0
# TODO check if valid and color accordingly # TODO check if valid and color accordingly
greek = "alpha beta gamma delta epsilon varepsilon zeta eta theta vartheta iota kappa lambda mu nu xi pi rho sigma tau upsilon phi varphi chi psi omega".split(" ") greek = "alpha beta gamma delta epsilon varepsilon zeta eta theta vartheta iota kappa lambda mu nu xi pi rho sigma tau upsilon phi varphi chi psi omega".split(" ")
...@@ -85,7 +94,15 @@ def variable_closure(variables, casify): ...@@ -85,7 +94,15 @@ def variable_closure(variables, casify):
def function_closure(functions, casify): def function_closure(functions, casify):
"""
Wrap `render_function` so it knows the functions allowed.
"""
def render_function(children): def render_function(children):
"""
Escape function names and give proper formatting to exceptions.
The exceptions being 'sqrt', 'log2', and 'log10' as of now.
"""
fname = children[0].latex fname = children[0].latex
if casify(fname) not in functions: if casify(fname) not in functions:
pass pass
...@@ -114,6 +131,11 @@ def function_closure(functions, casify): ...@@ -114,6 +131,11 @@ def function_closure(functions, casify):
def render_power(children): def render_power(children):
"""
Combine powers so that the latex is wrapped in curly braces correctly.
If you have 'a^(b+c)' don't include that last set of parens ('a^{b+c}').
"""
children_latex = [k.latex for k in children if k.latex != "^"] children_latex = [k.latex for k in children if k.latex != "^"]
children_latex[-1] = children[-1].sans_parens children_latex[-1] = children[-1].sans_parens
...@@ -123,6 +145,9 @@ def render_power(children): ...@@ -123,6 +145,9 @@ def render_power(children):
def render_parallel(children): def render_parallel(children):
"""
Simply combine elements with a double vertical line.
"""
children_latex = [k.latex for k in children if k.latex != "||"] children_latex = [k.latex for k in children if k.latex != "||"]
latex = r"\|".join(children_latex) latex = r"\|".join(children_latex)
tall = any(k.tall for k in children) tall = any(k.tall for k in children)
...@@ -130,7 +155,11 @@ def render_parallel(children): ...@@ -130,7 +155,11 @@ def render_parallel(children):
def render_frac(numerator, denominator): def render_frac(numerator, denominator):
# subtlety: avoid parens if there is only thing in that part r"""
Given a list of elements in the numerator and denominator, return a '\frac'
Avoid parens if there is only thing in that part
"""
if len(numerator) == 1: if len(numerator) == 1:
num_latex = numerator[0].sans_parens num_latex = numerator[0].sans_parens
else: else:
...@@ -146,6 +175,13 @@ def render_frac(numerator, denominator): ...@@ -146,6 +175,13 @@ def render_frac(numerator, denominator):
def render_product(children): def render_product(children):
r"""
Format products and division nicely.
That is, group bunches of adjacent, equal operators. For every time it
switches from numerator to denominator, call `render_frac`. Join these
groupings by '\cdot's.
"""
position = "numerator" # or denominator position = "numerator" # or denominator
fraction_mode_ever = False fraction_mode_ever = False
numerator = [] numerator = []
...@@ -186,6 +222,9 @@ def render_product(children): ...@@ -186,6 +222,9 @@ def render_product(children):
def render_sum(children): def render_sum(children):
"""
Combine elements, including their operators.
"""
children_latex = [k.latex for k in children] children_latex = [k.latex for k in children]
latex = "".join(children_latex) latex = "".join(children_latex)
tall = any(k.tall for k in children) tall = any(k.tall for k in children)
...@@ -193,6 +232,9 @@ def render_sum(children): ...@@ -193,6 +232,9 @@ def render_sum(children):
def render_atom(children): def render_atom(children):
"""
Properly handle parens, otherwise this is trivial.
"""
parens = None parens = None
if children[0].latex in "([{": if children[0].latex in "([{":
parens = children[0].latex parens = children[0].latex
...@@ -206,6 +248,11 @@ def render_atom(children): ...@@ -206,6 +248,11 @@ def render_atom(children):
def latex_preview(math_expr, variables=(), functions=(), case_sensitive=False): def latex_preview(math_expr, variables=(), functions=(), case_sensitive=False):
"""
Convert `math_expr` into latex, guaranteeing its parse-ability.
Analagous to `evaluator`.
"""
# No need to go further # No need to go further
if math_expr.strip() == "": if math_expr.strip() == "":
return "<nada/>" return "<nada/>"
......
...@@ -9,7 +9,13 @@ import pyparsing ...@@ -9,7 +9,13 @@ import pyparsing
class PreviewTestUtility(unittest.TestCase): class PreviewTestUtility(unittest.TestCase):
"""
Provide utilities for the preview test cases.
"""
def assert_latex_rendered(self, to_be_tested, tall=False, latex=None, sans_parens=None): def assert_latex_rendered(self, to_be_tested, tall=False, latex=None, sans_parens=None):
"""
Compare a `LatexRendered` object against the data it should store.
"""
if sans_parens is None: if sans_parens is None:
sans_parens = latex sans_parens = latex
self.assertEquals(to_be_tested.sans_parens, sans_parens) self.assertEquals(to_be_tested.sans_parens, sans_parens)
...@@ -18,68 +24,103 @@ class PreviewTestUtility(unittest.TestCase): ...@@ -18,68 +24,103 @@ class PreviewTestUtility(unittest.TestCase):
class LatexRenderedTest(PreviewTestUtility): class LatexRenderedTest(PreviewTestUtility):
"""
Test the initializing code for LatexRendered.
Specifically that it stores the correct data and handles parens well.
"""
def test_simple(self): def test_simple(self):
"""
Test that the data values are stored without changing.
"""
math = 'x^2' math = 'x^2'
obj = preview.LatexRendered(math, tall=True) obj = preview.LatexRendered(math, tall=True)
self.assert_latex_rendered(obj, tall=True, latex=math) self.assert_latex_rendered(obj, tall=True, latex=math)
def _each_parens(self, with_parens, math, parens, tall=False): def _each_parens(self, with_parens, math, parens, tall=False):
"""
Helper method to test the way parens are wrapped.
"""
obj = preview.LatexRendered(math, parens=parens, tall=tall) obj = preview.LatexRendered(math, parens=parens, tall=tall)
self.assert_latex_rendered(obj, tall=tall, latex=with_parens, sans_parens=math) self.assert_latex_rendered(obj, tall=tall, latex=with_parens, sans_parens=math)
def test_parens(self): def test_parens(self):
""" Test curvy parens. """
self._each_parens('(x+y)', 'x+y', '(') self._each_parens('(x+y)', 'x+y', '(')
def test_brackets(self): def test_brackets(self):
""" Test brackets. """
self._each_parens('[x+y]', 'x+y', '[') self._each_parens('[x+y]', 'x+y', '[')
def test_squiggles(self): def test_squiggles(self):
""" Test curly braces. """
self._each_parens(r'\{x+y\}', 'x+y', '{') self._each_parens(r'\{x+y\}', 'x+y', '{')
def test_parens_tall(self): def test_parens_tall(self):
""" Test curvy parens with the tall parameter. """
self._each_parens(r'\left(x^y\right)', 'x^y', '(', tall=True) self._each_parens(r'\left(x^y\right)', 'x^y', '(', tall=True)
def test_brackets_tall(self): def test_brackets_tall(self):
""" Test brackets, also tall. """
self._each_parens(r'\left[x^y\right]', 'x^y', '[', tall=True) self._each_parens(r'\left[x^y\right]', 'x^y', '[', tall=True)
def test_squiggles_tall(self): def test_squiggles_tall(self):
""" Test tall curly braces. """
self._each_parens(r'\left\{x^y\right\}', 'x^y', '{', tall=True) self._each_parens(r'\left\{x^y\right\}', 'x^y', '{', tall=True)
def test_bad_parens(self): def test_bad_parens(self):
""" Check that we get an error with invalid parens. """
with self.assertRaisesRegexp(Exception, 'Unknown parenthesis'): with self.assertRaisesRegexp(Exception, 'Unknown parenthesis'):
preview.LatexRendered('x^2', parens='not parens') preview.LatexRendered('x^2', parens='not parens')
def _call_render_method(render_method, string_children): def _call_render_method(render_method, string_children):
"""
Call `render_method` with LatexRendered version of `string_children`
"""
children = [preview.LatexRendered(k) for k in string_children] children = [preview.LatexRendered(k) for k in string_children]
return render_method(children) return render_method(children)
class RenderMethodsTest(PreviewTestUtility): class RenderMethodsTest(PreviewTestUtility):
"""
Test each of the `render_X` methods.
They take a list of `LatexRendered`s and, depending on the type of node,
combine them all together into one `LatexRendered`.
For the simplest of cases, call them with `_call_render_method` above.
"""
def test_number_simple(self): def test_number_simple(self):
""" Simple numbers should pass through `render_number`. """
out = _call_render_method(preview.render_number, ['3.1415']) out = _call_render_method(preview.render_number, ['3.1415'])
self.assert_latex_rendered(out, latex='3.1415') self.assert_latex_rendered(out, latex='3.1415')
def test_number_suffix(self): def test_number_suffix(self):
""" Suffixes should be escaped in `render_number`. """
out = _call_render_method(preview.render_number, ['1.618', 'k']) out = _call_render_method(preview.render_number, ['1.618', 'k'])
self.assert_latex_rendered(out, latex=r'1.618\text{k}') self.assert_latex_rendered(out, latex=r'1.618\text{k}')
def test_variable_simple(self): def test_variable_simple(self):
""" Simple valid variables should pass through `render_variable`. """
render_variable = preview.variable_closure(['x', 'y'], lambda x: x.lower()) render_variable = preview.variable_closure(['x', 'y'], lambda x: x.lower())
out = _call_render_method(render_variable, ['x']) out = _call_render_method(render_variable, ['x'])
self.assert_latex_rendered(out, latex='x') self.assert_latex_rendered(out, latex='x')
def test_greek(self): def test_greek(self):
""" Variable names that are greek should be formatted accordingly. """
render_variable = preview.variable_closure(['pi'], lambda x: x.lower()) render_variable = preview.variable_closure(['pi'], lambda x: x.lower())
out = _call_render_method(render_variable, ['pi']) out = _call_render_method(render_variable, ['pi'])
self.assert_latex_rendered(out, latex=r'\pi ') self.assert_latex_rendered(out, latex=r'\pi ')
def test_function_simple(self): def test_function_simple(self):
""" Valid function names should be escaped in `render_function`. """
render_function = preview.function_closure(['f'], lambda x: x.lower()) render_function = preview.function_closure(['f'], lambda x: x.lower())
out = _call_render_method(render_function, ['f', 'x']) out = _call_render_method(render_function, ['f', 'x'])
self.assert_latex_rendered(out, latex=r'\text{f}(x)') self.assert_latex_rendered(out, latex=r'\text{f}(x)')
def test_function_tall(self): def test_function_tall(self):
r""" Functions surrounding a tall element should have \left, \right """
render_function = preview.function_closure(['f'], lambda x: x.lower()) render_function = preview.function_closure(['f'], lambda x: x.lower())
out = render_function([ out = render_function([
preview.LatexRendered('f'), preview.LatexRendered('f'),
...@@ -88,21 +129,25 @@ class RenderMethodsTest(PreviewTestUtility): ...@@ -88,21 +129,25 @@ class RenderMethodsTest(PreviewTestUtility):
self.assert_latex_rendered(out, latex=r'\text{f}\left(x^2\right)', tall=True) self.assert_latex_rendered(out, latex=r'\text{f}\left(x^2\right)', tall=True)
def test_function_sqrt(self): def test_function_sqrt(self):
""" Sqrt function should be handled specially. """
render_function = preview.function_closure(['sqrt'], lambda x: x.lower()) render_function = preview.function_closure(['sqrt'], lambda x: x.lower())
out = _call_render_method(render_function, ['sqrt', 'x']) out = _call_render_method(render_function, ['sqrt', 'x'])
self.assert_latex_rendered(out, latex=r'\sqrt{x}') self.assert_latex_rendered(out, latex=r'\sqrt{x}')
def test_function_log10(self): def test_function_log10(self):
""" log10 function should be handled specially. """
render_function = preview.function_closure(['log10'], lambda x: x.lower()) render_function = preview.function_closure(['log10'], lambda x: x.lower())
out = _call_render_method(render_function, ['log10', 'x']) out = _call_render_method(render_function, ['log10', 'x'])
self.assert_latex_rendered(out, latex=r'\log_{10}(x)') self.assert_latex_rendered(out, latex=r'\log_{10}(x)')
def test_function_log2(self): def test_function_log2(self):
""" log2 function should be handled specially. """
render_function = preview.function_closure(['log2'], lambda x: x.lower()) render_function = preview.function_closure(['log2'], lambda x: x.lower())
out = _call_render_method(render_function, ['log2', 'x']) out = _call_render_method(render_function, ['log2', 'x'])
self.assert_latex_rendered(out, latex=r'\log_2(x)') self.assert_latex_rendered(out, latex=r'\log_2(x)')
def test_power_simple(self): def test_power_simple(self):
""" `render_power` should wrap the elements with braces correctly. """
out = _call_render_method( out = _call_render_method(
preview.render_power, preview.render_power,
['x', '^', 'y', '^', '2'] ['x', '^', 'y', '^', '2']
...@@ -110,6 +155,7 @@ class RenderMethodsTest(PreviewTestUtility): ...@@ -110,6 +155,7 @@ class RenderMethodsTest(PreviewTestUtility):
self.assert_latex_rendered(out, latex='x^{y^{2}}', tall=True) self.assert_latex_rendered(out, latex='x^{y^{2}}', tall=True)
def test_power_parens(self): def test_power_parens(self):
""" `render_power` should ignore the parenthesis of the last math. """
children = ['x', '^', 'y', '^'] # (x+y) children = ['x', '^', 'y', '^'] # (x+y)
children = [preview.LatexRendered(k) for k in children] children = [preview.LatexRendered(k) for k in children]
children.append(preview.LatexRendered('x+y', parens='(')) children.append(preview.LatexRendered('x+y', parens='('))
...@@ -118,20 +164,24 @@ class RenderMethodsTest(PreviewTestUtility): ...@@ -118,20 +164,24 @@ class RenderMethodsTest(PreviewTestUtility):
self.assert_latex_rendered(out, latex='x^{y^{x+y}}', tall=True) self.assert_latex_rendered(out, latex='x^{y^{x+y}}', tall=True)
def test_parallel(self): def test_parallel(self):
r""" `render_power` should combine its elements with '\|'. """
out = _call_render_method(preview.render_parallel, ['x', '||', 'y']) out = _call_render_method(preview.render_parallel, ['x', '||', 'y'])
self.assert_latex_rendered(out, latex=r'x\|y') self.assert_latex_rendered(out, latex=r'x\|y')
def test_product_mult_only(self): def test_product_mult_only(self):
r""" `render_product` should combine a product with a '\cdot'. """
out = _call_render_method(preview.render_product, ['x', '*', 'y']) out = _call_render_method(preview.render_product, ['x', '*', 'y'])
self.assert_latex_rendered(out, latex=r'x\cdot y') self.assert_latex_rendered(out, latex=r'x\cdot y')
def test_product_big_frac(self): def test_product_big_frac(self):
""" `render_product` should combine a fraction with '\frac'. """
out = _call_render_method( out = _call_render_method(
preview.render_product, list('w*x/y/z') preview.render_product, list('w*x/y/z')
) )
self.assert_latex_rendered(out, latex=r'\frac{w\cdot x}{y\cdot z}', tall=True) self.assert_latex_rendered(out, latex=r'\frac{w\cdot x}{y\cdot z}', tall=True)
def test_product_single_frac(self): def test_product_single_frac(self):
""" `render_product` should ignore parens if they are extraneous. """
out = preview.render_product([ out = preview.render_product([
preview.LatexRendered('x+1', parens='('), preview.LatexRendered('x+1', parens='('),
preview.LatexRendered('/'), preview.LatexRendered('/'),
...@@ -140,16 +190,19 @@ class RenderMethodsTest(PreviewTestUtility): ...@@ -140,16 +190,19 @@ class RenderMethodsTest(PreviewTestUtility):
self.assert_latex_rendered(out, latex=r'\frac{x+1}{y+1}', tall=True) self.assert_latex_rendered(out, latex=r'\frac{x+1}{y+1}', tall=True)
def test_product_keep_going(self): def test_product_keep_going(self):
""" `render_product` should split into many '\frac's when needed. """
out = _call_render_method( out = _call_render_method(
preview.render_product, list('p/q*r/s*t') preview.render_product, list('p/q*r/s*t')
) )
self.assert_latex_rendered(out, latex=r'\frac{p}{q}\cdot \frac{r}{s}\cdot t', tall=True) self.assert_latex_rendered(out, latex=r'\frac{p}{q}\cdot \frac{r}{s}\cdot t', tall=True)
def test_sum(self): def test_sum(self):
""" `render_sum` should combine its elements. """
out = _call_render_method(preview.render_sum, list('-a+b-c+d')) out = _call_render_method(preview.render_sum, list('-a+b-c+d'))
self.assert_latex_rendered(out, latex=r'-a+b-c+d') self.assert_latex_rendered(out, latex=r'-a+b-c+d')
def test_sum_tall(self): def test_sum_tall(self):
""" `render_sum` should pass on `tall`-ness. """
out = preview.render_sum([ out = preview.render_sum([
preview.LatexRendered('x'), preview.LatexRendered('x'),
preview.LatexRendered('+'), preview.LatexRendered('+'),
...@@ -158,24 +211,35 @@ class RenderMethodsTest(PreviewTestUtility): ...@@ -158,24 +211,35 @@ class RenderMethodsTest(PreviewTestUtility):
self.assert_latex_rendered(out, latex='x+y^2', tall=True) self.assert_latex_rendered(out, latex='x+y^2', tall=True)
def test_atom_simple(self): def test_atom_simple(self):
""" `render_atom` should pass through items without parens. """
out = _call_render_method(preview.render_atom, list('x')) out = _call_render_method(preview.render_atom, list('x'))
self.assert_latex_rendered(out, latex='x') self.assert_latex_rendered(out, latex='x')
def test_atom_parens(self): def test_atom_parens(self):
""" Items wrapped in parens should have a `sans_parens` value. """
out = _call_render_method(preview.render_atom, list('(x)')) out = _call_render_method(preview.render_atom, list('(x)'))
self.assert_latex_rendered(out, latex='(x)', sans_parens='x') self.assert_latex_rendered(out, latex='(x)', sans_parens='x')
class LatexPreviewTest(unittest.TestCase): class LatexPreviewTest(unittest.TestCase):
""" """
Run tests for preview.latex_preview Run integrative tests for `latex_preview`.
All functionality was tested `RenderMethodsTest`, but see if it combines
all together correctly.
""" """
def test_no_input(self): def test_no_input(self):
"""
With no input (including just whitespace), see that no error is thrown.
"""
self.assertEquals('<nada/>', preview.latex_preview('')) self.assertEquals('<nada/>', preview.latex_preview(''))
self.assertEquals('<nada/>', preview.latex_preview(' ')) self.assertEquals('<nada/>', preview.latex_preview(' '))
self.assertEquals('<nada/>', preview.latex_preview(' \t ')) self.assertEquals('<nada/>', preview.latex_preview(' \t '))
def test_complicated(self): def test_complicated(self):
"""
Given complicated input, ensure that exactly the correct string is made.
"""
self.assertEquals( self.assertEquals(
preview.latex_preview('11*f(x)+x^2*(3||4)/sqrt(pi)'), preview.latex_preview('11*f(x)+x^2*(3||4)/sqrt(pi)'),
r'11\cdot \text{f}(x)+\frac{x^{2}\cdot (3\|4)}{\sqrt{\pi }}' r'11\cdot \text{f}(x)+\frac{x^{2}\cdot (3\|4)}{\sqrt{\pi }}'
...@@ -207,7 +271,7 @@ class LatexPreviewTest(unittest.TestCase): ...@@ -207,7 +271,7 @@ class LatexPreviewTest(unittest.TestCase):
try: try:
preview.latex_preview(math) preview.latex_preview(math)
except pyparsing.ParseException: except pyparsing.ParseException:
pass # This is what we were expecting. pass # This is what we were expecting. (not excepting :P)
except Exception as error: # pragma: no cover except Exception as error: # pragma: no cover
bad_exceptions[math] = error bad_exceptions[math] = error
else: # pragma: no cover else: # pragma: no cover
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment