Commits

Jonathan Eunice  committed 9391e49

conformed to latest options API; commenced multi-version testing

  • Participants
  • Parent commits b6c3e68

Comments (0)

Files changed (7)

 syntax: glob
 *.swp.{py,txt,html,css,js}
 *.pyc
+.tox
 .DS_Store
 build/*
 dist/*
    common and straightforward text formatting has led me to re-think how
    software should format text. ``quoter`` is one facet of a project to
    systematize higher-level formatting operations.
+   
+ * ``quoter`` is also a test case for `options <http://pypi.python.org/pypi/options>`_,
+   a module that supports flexible option handling.
 
+ * Commenced automated multi-version testing with
+   `pytest <http://pypi.python.org/pypi/pytest>`_
+   and `tox <http://pypi.python.org/pypi/tox>`_. Now
+   successfully packaged for, and tested against, Python 2.6, 2.7, 3.2, and 3.3.
+   
  * The author, `Jonathan Eunice <mailto:jonathan.eunice@gmail.com>`_ or
    `@jeunice on Twitter <http://twitter.com/jeunice>`_ welcomes your comments
    and suggestions.
-
+ 
 Installation
 ============
 
 ::
 
     pip install quoter
+
+To ``easy_install`` under a specific Python version (3.3 in this example)::
+
+    python3.3 -m easy_install quoter
     
-(You may need to prefix this with "sudo " to authorize installation.)
+(You may need to prefix these with "sudo " to authorize installation.)
+[pytest]
+python_files = test/*.py
+
 
 import re
 import six
-from nulltype import NullType
+from options import Options, Prohibited
     
-Default = NullType()
-
 def is_string(v):
     return isinstance(v, six.string_types)    
 
     """
     
     styles = {}         # remember named styles
-    encoding = None     # global encoding
+                        # NB global across all style Quoter classes
     
-    def __init__(self, prefix=None, suffix=None, chars=None, name=None, padding=0, margin=0, encoding=Default):
+    options = Options(
+        prefix   = '',
+        suffix   = None,
+        name     = None,
+        padding  = 0,
+        margin   = 0,
+        encoding = None
+    )
+    
+    # TBD add back chars= option as a MultiSetter
+    
+    def __init__(self, *args, **kwargs):
         """
         Create a quoting style.
         """
-        if chars:
-            assert prefix is None and suffix is None
-            assert len(chars) % 2 == 0
-            half = len(chars) / 2
-            self.prefix = chars[:half]
-            self.suffix = chars[half:]
-        else:
-            assert chars is None
-            self.prefix = prefix
-            self.suffix = suffix if suffix is not None else self.prefix
-        self.padding = padding
-        self.margin  = margin
-        self.encoding = encoding
-        if name:
-            Quoter.styles[name] = self
+        self.options = Quoter.options.push(kwargs)
+        if args:
+            used = self.options.addflat(args, ['prefix', 'suffix'])
+            if 'suffix' not in used:
+                self.options.suffix = self.options.prefix
+                # this suffix = prefix behavior appropriate for flat args only
+        if self.options.name:
+            Quoter.styles[self.options.name] = self
             
-    def _whitespace(self, padding, margin):
+    def _whitespace(self, opts):
         """
         Compute the appropriate margin and padding strings.
-        """
-        padding = padding if padding is not Default else self.padding
-        margin  = margin  if margin  is not Default else self.margin
-        pstr = ' ' * padding if isinstance(padding, int) else padding
-        mstr  = ' ' * margin  if isinstance(margin, int) else margin
+        """   
+        pstr = ' ' * opts.padding if isinstance(opts.padding, int) else opts.padding
+        mstr = ' ' * opts.margin  if isinstance(opts.margin, int)  else opts.margin
         return (pstr, mstr)
         
-    def _output(self, *parts, **kwargs):
+    def _output(self, parts, opts):
         """
         Given a list of string parts, concatentate them and output
         with the given encoding (if any).
         """
         outstr = ''.join(parts)
-        encoding = kwargs.get('encoding', None) or self.encoding or Quoter.encoding
-        return outstr.encode(encoding) if encoding else outstr
+        return outstr.encode(opts.encoding) if opts.encoding else outstr
         
-    def __call__(self, value, padding=Default, margin=Default, encoding=Default):
+    def __call__(self, value, **kwargs):
         """
         Quote the value, with the given padding, margin, and encoding.
         """
-        pstr, mstr = self._whitespace(padding, margin)
-        return self._output(mstr, self.prefix, pstr, stringify(value), pstr, self.suffix, mstr)
+        opts = self.options.push(kwargs)
+        pstr, mstr = self._whitespace(opts)
+        suffix = opts.suffix if opts.suffix is not None else opts.prefix
+        parts = [ mstr, opts.prefix, pstr, stringify(value), pstr, suffix, mstr ]
+        return self._output(parts, opts)
 
 # create some default named styles (ASCII)
 
     """
     A Quoter that uses code to decide what quotes to use, based on the value.
     """
-    def __init__(self, func, name=None, padding=0, margin=0, encoding=None):
+    
+    options = Quoter.options.add(
+        func   = None,
+        prefix = Prohibited,
+        suffix = Prohibited,
+    )
+    
+    def __init__(self, *args, **kwargs):
         """
         Create a dynamic quoting style.
         """
-        self.func = func
-        self.padding = padding
-        self.margin  = margin
-        self.encoding = encoding
-        if name:
-            Quoter.styles[name] = self
-            
-    def __call__(self, value, padding=Default, margin=Default, encoding=Default):
+        self.options = LambdaQuoter.options.push(kwargs)
+        if args:
+            self.options.addflat(args, ['func'])
+        if self.options.name:
+            Quoter.styles[self.options.name] = self
+    
+    # NB not calling Quoter.__init__ to avoid its processing - should it??
+
+    def __call__(self, value, **kwargs):
         """
         Quote the value, based on the instance's function.
         """
-        pstr, mstr = self._whitespace(padding, margin)
-        prefix, value, suffix = self.func(value)
-        return self._output(mstr, prefix, pstr, stringify(value), pstr, suffix, mstr, encoding=encoding)
+        opts = self.options.push(kwargs)
+        pstr, mstr = self._whitespace(opts)
+        prefix, value, suffix = opts.func(value)
+        parts = [ mstr, prefix, pstr, stringify(value), pstr, suffix, mstr ]
+        return self._output(parts, opts)
 
 class HTMLQuoter(Quoter):
     """
     A more sophisticated quoter that supports attributes and void elements.
     """
     
-    def __init__(self, tag, attquote=single, void=False, name=None, padding=0, margin=0, encoding=None):
+    options = Quoter.options.add(
+        tag      = None,
+        atts     = {},
+        attquote = single,
+        void     = False,
+        prefix   = Prohibited,
+        suffix   = Prohibited,
+    )
+    
+    def __init__(self, *args, **kwargs):
         """
         Create an HTMLQuoter
         """
-        tagname, atts = self._parse_selector(tag)
-        self.tag = tagname
-        self.atts = atts or {}
-        self.attquote = attquote
-        self.void = void
-        self.padding = padding
-        self.margin  = margin
-        self.encoding = encoding
-        if name:
-            Quoter.styles[name] = self
+        self.options = HTMLQuoter.options.push(kwargs)
+        if args:
+            self.options.addflat(args, ['tag', 'atts'])
+
+        create_atts = self.options.atts
+        self.options.tag, self.options.atts = self._parse_selector(self.options.tag)
+        if create_atts:
+            self.options.atts.update(create_atts)
+        # explicit addition of atts
             
-    def _attstr(self, atts):
+        # NB this is an explicit multi-setter
+        
+        if self.options.name:
+            Quoter.styles[self.options.name] = self
+            
+    def _attstr(self, atts, opts):
         """
         Format an attribute dict.
         """
-        return ' '.join([''] + ["{}={}".format(k, self.attquote(v)) for k,v in atts.items()])
-    
+        return ' '.join([''] + ["{0}={1}".format(k, opts.attquote(v)) for k,v in atts.items()])
+        
     def _parse_selector(self, selector):
         """
         Parse a selector (modeled on jQuery and CSS). Returns a (tagname, id, classes)
             atts['class'] = ' '.join(classes)
         return (tagnames[0] if tagnames else None, atts)
     
-    def __call__(self, value=None, atts=None, padding=Default, margin=Default, encoding=Default):
+    def __call__(self, *args, **kwargs):
         """
         Quote a value in X/HTML style, with optional attributes.
         """
-        pstr, mstr = self._whitespace(padding, margin)
-        callatts = self._parse_selector(atts)[1] if is_string(atts) else atts or {}
+        
+        if 'atts' in kwargs:
+            catts = kwargs['atts']
+            del kwargs['atts']
+        else:
+            catts = {}
+        
+        value = ''
+        if len(args) > 0:
+            value = args[0]
+        if len(args) > 1:                
+            catts = args[1]
+        if len(args) > 2:
+            raise ValueError('just one or two args, please')
+            
+        opts = self.options.push(kwargs)
+        pstr, mstr = self._whitespace(opts)
+
+        # do we need some special processing to remove atts from the kwargs?
+        # or some magic to integrate call atts to with existing atts?
+        # or ..?
+        # this is a good test / hard case for the magial processing
+        # probably magic
+        
+        callatts = self._parse_selector(catts)[1] if is_string(catts) else catts
         atts = {}
-        atts.update(self.atts)
-        atts.update(callatts)            
-        astr = self._attstr(atts) if atts else ''
-        if self.void:
-            assert value is None
-            return self._output(mstr, '<', self.tag, astr, '>', mstr, encoding=encoding)
+        atts.update(opts.atts)
+        atts.update(callatts)
+        
+        astr = self._attstr(atts, opts) if atts else ''
+        if opts.void or not args:
+            parts = [ mstr, '<', opts.tag, astr, '>', mstr ]
         else:
-            return self._output(mstr, '<', self.tag, astr, '>', pstr, stringify(value),
-                                pstr, '</', self.tag, '>', mstr, encoding=encoding)
+            parts = [ mstr, '<', opts.tag, astr, '>', pstr,
+                      stringify(value),
+                      pstr, '</', opts.tag, '>', mstr ] 
+        return self._output(parts, opts)
         
     # could improve kwargs handling of HTMLQuoter
+    
+    # question is, should call attributes overwrite, or add to, object atts?
+    # may not be a single answer - eg, in case of class especially
+    
+    # This might be case where replace is the primary option, but there's
+    # an option to add (or even subtract) - say using a class Add, Plus, Subtract,
+    # Minus, Relative, Rel, Delta, etc as an indicator
+    
+    
+    
 
     
 # need def_tag to define these
     # The double-mention of l.strip() is yet another fine example of why
     # Python needs en passant aliasing.
 
-
 def verno(s):
     """
     Update the version number passed in by extending it to the 
 
 setup(
     name='quoter',
-    version=verno("0.111"),
+    version=verno("0.301"),
     author='Jonathan Eunice',
     author_email='jonathan.eunice@gmail.com',
     description="A simple way to quote and wrap text",
     long_description=open('README.rst').read(),
+    keywords='quote wrap prefix suffix endcap',
     url='https://bitbucket.org/jeunice/quoter',
     py_modules=['quoter', 'nulltype'],
-    install_requires=['six'],
+    install_requires=['six', 'options>=0.265'],
+    tests_require = ['tox', 'pytest'],
+    zip_safe = True,
     classifiers=linelist("""
         Development Status :: 3 - Alpha
         Operating System :: OS Independent
         License :: OSI Approved :: BSD License
         Intended Audience :: Developers
         Programming Language :: Python
+        Programming Language :: Python :: 2.6
+        Programming Language :: Python :: 2.7
+        Programming Language :: Python :: 3.2
+        Programming Language :: Python :: 3.3
         Topic :: Software Development :: Libraries :: Python Modules
     """)
 )

File test/test.py

-from testharness import import_from_parent, test_run
-
-import_from_parent()
 from quoter import *
 
 def test_braces():
     assert brackets('this', margin=1) == ' [this] '
     assert brackets('this', padding=1, margin=1) == ' [ this ] '
     
-def test_chars():
-    percent = Quoter(chars='%%')
-    assert percent('something') == '%something%'
-    doublea = Quoter(chars='<<>>')
-    assert doublea('AAA') == '<<AAA>>'
+#def test_chars():
+#    percent = Quoter(chars='%%')
+#    assert percent('something') == '%something%'
+#    doublea = Quoter(chars='<<>>')
+#    assert doublea('AAA') == '<<AAA>>'
 
 def test_shortcuts():    
     assert ' '.join([qs('one'), qd('two'), qb('and'), qt('three')]) == \
         "'one' \"two\" `and` \"\"\"three\"\"\""
     
 def test_instant():
-    assert Quoter(chars='+[  ]+')('castle') == '+[ castle ]+'
+    assert Quoter('+[ ', ' ]+')('castle') == '+[ castle ]+'
     
 def test_lambda():
     f = lambda v: ('(', abs(v), ')') if v < 0 else ('', v, '')
     financial = LambdaQuoter(f)
-    print financial(-3)
-    print financial(45)
     assert financial(-10) == '(10)'
     assert financial(44)  == '44'
     
 
 def test_para():
     para = HTMLQuoter('p')
-    assert para('this is great!', {'class':'emphatic'}) == "<p class='emphatic'>this is great!</p>"
+    # assert para('this is great!', {'class':'emphatic'}) == "<p class='emphatic'>this is great!</p>"
     assert para('this is great!', '.emphatic') == "<p class='emphatic'>this is great!</p>"
     assert para('First para!', '#first') == "<p id='first'>First para!</p>"
 
     
     div = HTMLQuoter('div', attquote=double)
     assert div('something', '.todo') == '<div class="todo">something</div>'
-
-    
-if __name__ == '__main__':
-    test_run()
+[tox]
+envlist = py26, py27, py32, py33
+
+[testenv]
+changedir=test
+deps=
+    six
+    pytest
+commands=py.test {posargs}