Bitbucket is a code hosting site with unlimited public and private repositories. We're also free for small teams!

Close

In dealing with text, one quotes values all the time. Single quotes. Double quotes. Curly quotes. Backticks. Funny Unicode quotes. HTML or XML markup code. Et cetera.

For simple cases, this isn't hard, and there a lot of ways to do it:

value = 'something'
print '{x}'.replace('x', value)             # {something}
print "'{}'".format(value)                  # 'value'
print "'" + value + "'"                     # 'value'
print "{}{}{}".format('"', value, '"')      # "value"
print ''.join(['"', value, '"'])            # "value"

But for such a simple, common task as wrapping values in surrounding text, it looks pretty ugly, it's very low-level, and it's easy to type the wrong character here or there. The ad hoc nature makes quoting tiresome and error-prone. It's never more so than when you're constructing multi-level quoted strings, such as Unix command line arguments, SQL commands, or HTML attributes.

So this module provides an clean, consistent, higher-level alternative. Beyond just a better API, it also provides a mechanism to pre-define quoting styles that can then be later easily reused.

Usage

from quoter import *

print single('this')       # 'this'
print double('that')       # "that"
print backticks('ls -l')   # `ls -l`
print braces('curlycue')   # {curlycue}

It pre-defines callable Quoters for a handful of the most common quoting styles:

  • braces {example}
  • brackets [example]
  • angles <example>
  • parens (example)
  • double "example"
  • single 'example'
  • backticks `example`
  • anglequote «example»
  • curlysingle ‘example’
  • curlysdouble “example”

But there are a huge number of ways you might want to wrap or quote text. Even considering just "quotation marks," there are well over a dozen. There are also numerous bracketing symbols in common use. That's to say nothing of the constructs seen in markup, programming, and templating languages. So quoter couldn't possibly provide an option for every possible quoting style. Instead, it provides a general-purpose mechanism for defining your own:

from quoter import Quoter

bars = Quoter('|')
print bars('x')                    # |x|

plus = Quoter('+','')
print plus('x')                    # +x

para = Quoter('<p>', '</p>')
print para('this is a paragraph')  # <p>this is a paragraph</p>

variable = Quoter('${', '}')
print variable('x')                # ${x}

Note that bars is specified with just one symbol. If only one is given, the prefix and suffix are considered to be identical. If you really only want a prefix or a suffix, and not both, then instantiate the Quoter with two, one of which is an empty string, as in plus above. For symmetrical quotes, where the length of the prefix and the suffix are the same, you can specify the prefix and suffix all in one go. The prefix will be the first half, the second, the second half

In most cases, it's cleaner and more efficient to define a style, but there's nothing preventing you from an on-the-fly usage:

print Quoter('+[ ', ' ]+')('castle')   # +[ castle ]+

Formatting and Encoding

The Devil, as they say, is in the details. We often don't just want quote marks wrapped around values. We also want those values set apart from the rest of the text. quoter supports this with padding and margin settings patterned on the CSS box model. In CSS, moving out from content one finds padding, a border, and then a margin. Padding can be thought of as an internal margin, and the prefix and suffix strings like the border. With that in mind:

print braces('this')                      # '{this}'
print braces('this', padding=1)           # '{ this }'
print braces('this', margin=1)            # ' {this} '
print braces('this', padding=1, margin=1) # ' { this } '

If desired, the padding and margin can be given as strings, though usually they will be integers specifying the number of spaces surrounding the text.

One can also define the encoding used for each call, per instance, or globally. If some of your quote symbols use Unicode characters, yet your output medium doesn't support them directly, this is an easy fix. E.g.:

Quoter.options.encoding = 'utf-8'
print curlydouble('something something')

Now curlydouble will output UTF-8 bytes. But in general, you should work in Unicode strings in Python, encoding or decoding only at the time of input and output, not as each piece of content is constructed.

Shortcuts

One often sees very long function calls and expressions as text parts are being assembled. In order to reduce this problem, quoter defines aliases for single, double, and triple quoting, as well as backticks:

from quoter import qs, qd, qt, qb

print qs('one'), qd('two'), qb('and'), qt('three')
# 'one' "two" `and` """three"""

You can, of course, define your own aliases as well, and/or redefine existing styles. If, for example, you like braces but wish it added a padding space by default, it's simple to redefine:

braces = Quoter('{', '}', padding=1, name='braces')
print braces('braces plus spaces!')  # '{ braces plus spaces! }'

You could still get the no-padding variation with:

print braces('no space braces', padding=0) # '{no space braces}'

Clean Imports

As an organizational assist, quoters are available as named attributes of a pre-defined quote object. For those who like strict, minialist imports, this permits from quoter import quote without loss of generality. For example:

from quoter import quote

quote.double('test')    # "test"
quote.braces('test')    # {test}
# ...and so on...

Each of these can also serve like an instance of an enumerated type, specifying for a later time what kind of quoting you'd like. Then, at the time that quoter is needed, it can simply be called. E.g.:

preferred_quoting = quote.brackets

...

print preferred_quoting(data)

HTML

There is an extended quoting mode designed for XML and HTML construction.

Instead of prefix and suffix strings, they use tag names. Or more accurately, tag specifications. Like jQuery HTMLQuoter supports id and class attributes in a style similar to that of CSS selectors. This is a considerable help in Python, which defines and/or reserves some of the attribute names most used in HTML (to wit, class and id). Using the CSS selector style neatly gets around this annoyance--and is more compact to boot.

HTML quoting also understands that some elements are 'void', meaning they do not want or need closing tags.

So for example:

from quoter import *

print html.p('this is great!', {'class':'emphatic'})
print html.p('this is great!', '.emphatic')

print html.p('First para!', '#first')

You can also define your own customized quoters which can be called functionally or, if you name them, via the html. front-end.:

para_e = HTMLQuoter('p.emphatic', name='para_e')
print para_e('this is great!')
print html.para_e('this is great?', '.question')

print html.br()

yields:

<p class='emphatic'>this is great!</p>
<p class='emphatic'>this is great!</p>
<p id='first'>First para!</p>
<p class='emphatic'>this is great!</p>
<p class='question'>this is great?</p>
<br>

HTMLQuoter quotes attributes by default with single quotes. If you prefer double quotes, you may set them when the element is defined:

div = HTMLQuoter('div', attquote=double)

HTMLQuoter basically works, but buyer beware: It's not as well tested as the rest of the module.

XML

There is also an XMLQuoter with an xml front-end. It offers one additional attribute beyond HTMLQuoter: ns for namespaces. Thus:

item = XMLQuoter(tag='item', ns='inv', name='item inv_item')
print item('an item')
print xml.item('another')
print xml.inv_item('yet another')
print xml.thing('something')

yields:

<inv:item>an item</inv:item>
<inv:item>another</inv:item>
<inv:item>yet another</inv:item>
<thing>something</thing>

Note that xml.tagname auto-generates quoters just like html.tagname does on first use. There are also pre-defined utility methods such as html.comment() and xml.comment() for commenting purposes.

Named Styles

Quoting via the functional API or the attribute-accessed front-ends (quote, html, and xml) is probably the easiest way to go. But there's one more way. If you provide the name of a defined style via the style attribute, that's the style you get. So while quote('something') gives you single quotes by default ('something'), if you invoke it as quote('something', style='double'), you get double quoting as though you had used quote.double(...), double(...), or qd(...). This even works through named front.ends; quote.braces('something', style='double') still gets you "something". If you don't want to be confused by such double-bucky forms, don't use them. The best use-case for named styles is probably when you don't know how something will be quoted (or what tag it will use, in the HTML or XML case), but that decision is made dynamically. Then style=desired_style makes good sense.

Style names are stored in the class of the quoter. So all Quoter instances share the same named styles, as do HTMLQuoter, XMLQuoter, and LambdaQuoter.

Dynamic Quoters

It is possible to define Quoters that don't just concatenate text, but that examine it and provide dynamic rewriting on the fly. For example, in finance, one often wants to present numbers with a special formatting:

from quoter import LambdaQuoter

f = lambda v: ('(', abs(v), ')') if v < 0 else ('', v, '')
financial = LambdaQuoter(f)
print financial(-3)            # (3)
print financial(45)            # 45

password = LambdaQuoter(lambda v: ('', 'x' * len(v), ''))
print password('secret!')      # xxxxxxx

wf = lambda v:  ('**', v, '**') if v < 0 else ('', v, '')
warning = LambdaQuoter(wf, name='warning')
print warning(12)              # 12
print warning(-99)             # **-99**

The trick is instantiating LambdaQuoter with a callable (e.g. lambda expression or function) that accepts one value and returns a tuple of three values: the quote prefix, the value (possibly rewritten), and the suffix.

You can access LambdaQuoter named instances through lambdaq (because lambda is a reserved word). Given the code above, lambdaq.warning is active, for example.

LambdaQuoter is an edge case, arcing over towards being a general formatting function. That has the virtue of providing a consistent mechanism for tactical output transformation with built-in margin and padding support. But, one could argue that such full transformations are "a bridge too far" for a quoting module. So use the dynamic component of``quoter``, or not, as you see fit.

Notes

  • quoter provides simple transformations that could be alternatively implemented as a series of small functions. The problem is that such "little functions" tend to be constantly re-implemented, in different ways, and spread through many programs. That need to constantly re-implement such 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. See say and show for the larger effort.
  • quoter is also a test case for options, a module that supports flexible option handling. In fact, it is one of options most extensive test cases, in terms of subclassing and dealing with named styles.
  • In the future, additional quoting styles such as ones for Markdown or RST format styles might appear. It's not hard to subclass Quoter for new languages.
  • Automated multi-version testing is managed with the magnificent pytest and tox. Now successfully packaged for, and tested against, Python 2.6, 2.7, 3.2, and 3.3, as well as PyPy 2.1 (based on 2.7.3).
  • The author, Jonathan Eunice or @jeunice on Twitter welcomes your comments and suggestions.

Installation

pip install -U quoter

To easy_install under a specific Python version (3.3 in this example):

python3.3 -m easy_install --upgrade quoter

(You may need to prefix these with "sudo " to authorize installation.)

Recent activity

Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.