diff --git a/django/conf/admin_templates/admin_change_form.html b/django/conf/admin_templates/admin_change_form.html
index 120ebd29bd..b40e04328c 100644
--- a/django/conf/admin_templates/admin_change_form.html
+++ b/django/conf/admin_templates/admin_change_form.html
@@ -2,9 +2,7 @@
{% load admin_modify %}
{% load adminmedia %}
{% block extrahead %}
- {% for js in javascript_imports %}
- {% include_admin_script js %}
- {% endfor %}
+{% for js in javascript_imports %}{% include_admin_script js %}{% endfor %}
{% endblock %}
{% block coltype %}{{ coltype }}{% endblock %}
{% block bodyclass %}{{app_label}}-{{object_name.lower}} change-form{% endblock %}
@@ -15,36 +13,21 @@
{% if add %}Add {{verbose_name}}{% else %}{{original|striptags|truncatewords:"18"}}{% endif %}
{% endif %}{% endblock %}
-
{% block content %}
-{% if change %}
- {% if not is_popup %}
-
- {% endif %}
-{% endif %}
-
+{% if change %}{% if not is_popup %}
+
+{% endif %}{% endif %}
{% endif %}
{% endif %}
-
-
-{% for relation in inline_related_objects %}
- {% edit_inline relation %}
-{% endfor %}
+{% for related_object in inline_related_objects %}{% edit_inline related_object %}{% endfor %}
{% submit_row %}
@@ -88,7 +66,7 @@
"
-
#match starts of lines
newline_re = re.compile("^", re.M);
@@ -85,7 +84,6 @@ newline_re = re.compile("^", re.M);
tag_re = re.compile('(%s.*?%s|%s.*?%s)' % (re.escape(BLOCK_TAG_START), re.escape(BLOCK_TAG_END),
re.escape(VARIABLE_TAG_START), re.escape(VARIABLE_TAG_END)))
-
# global dict used by register_tag; maps custom tags to callback functions
registered_tags = {}
@@ -125,8 +123,15 @@ class Template:
def compile_string(template_string, filename):
"Compiles template_string into NodeList ready for rendering"
- tokens = tokenize(template_string, filename)
- parser = Parser(tokens)
+ if DEBUG:
+ lexer_factory = DebugLexer
+ parser_factory = DebugParser
+ else:
+ lexer_factory = Lexer
+ parser_factory = Parser
+
+ lexer = lexer_factory(template_string, filename)
+ parser = parser_factory(lexer.tokenize())
return parser.parse()
class Context:
@@ -176,82 +181,100 @@ class Context:
self.dicts = [other_dict] + self.dicts
class Token:
- def __init__(self, token_type, contents, source):
+ def __init__(self, token_type, contents):
"The token_type must be TOKEN_TEXT, TOKEN_VAR or TOKEN_BLOCK"
self.token_type, self.contents = token_type, contents
- self.source = source
def __str__(self):
- return '<%s token: "%s..." from %s, line %d>' % (
+ return '<%s token: "%s...">' % (
{TOKEN_TEXT:'Text', TOKEN_VAR:'Var', TOKEN_BLOCK:'Block'}[self.token_type],
- self.contents[:20].replace('\n', ''),
- self.source[0], self.source[1]
+ self.contents[:20].replace('\n', '')
)
-def find_linebreaks(template_string):
- for match in newline_re.finditer(template_string):
- yield match.start()
-
-def tokenize(template_string, filename):
- "Return a list of tokens from a given template_string"
+class Lexer(object):
+ def __init__(self, template_string, filename):
+ self.template_string = template_string
+ self.filename = filename
- token_tups = []
- upto = 0
- line = 1
- #TODO:Py2.4 generator expression
- linebreaks = find_linebreaks(template_string)
- next_linebreak = linebreaks.next()
- try:
- for match in tag_re.finditer(template_string):
- start, end = match.span()
- if start > upto:
- token_tups.append( (template_string[upto:start], line) )
- upto = start
+ def tokenize(self):
+ "Return a list of tokens from a given template_string"
+ bits = filter(None, tag_re.split(self.template_string))
+ return map(self.create_token, bits)
+
+ def create_token(self,token_string):
+ "Convert the given token string into a new Token object and return it"
+ if token_string.startswith(VARIABLE_TAG_START):
+ token = Token(TOKEN_VAR, token_string[len(VARIABLE_TAG_START):-len(VARIABLE_TAG_END)].strip())
+ elif token_string.startswith(BLOCK_TAG_START):
+ token = Token(TOKEN_BLOCK, token_string[len(BLOCK_TAG_START):-len(BLOCK_TAG_END)].strip())
+ else:
+ token = Token(TOKEN_TEXT, token_string)
+ return token
+
+class DebugLexer(Lexer):
+ def __init__(self, template_string, filename):
+ super(DebugLexer,self).__init__(template_string, filename)
+
+ def find_linebreaks(self, template_string):
+ for match in newline_re.finditer(template_string):
+ yield match.start()
+
+ def tokenize(self):
+ "Return a list of tokens from a given template_string"
+
+ token_tups = []
+ upto = 0
+ line = 1
+ #TODO:Py2.4 generator expression
+ linebreaks = self.find_linebreaks(self.template_string)
+ next_linebreak = linebreaks.next()
+ try:
+ for match in tag_re.finditer(self.template_string):
+ start, end = match.span()
+ if start > upto:
+ token_tups.append( (self.template_string[upto:start], line) )
+ upto = start
+
+ while next_linebreak <= upto:
+ next_linebreak = linebreaks.next()
+ line += 1
+ token_tups.append( (self.template_string[start:end], line) )
+ upto = end
+
while next_linebreak <= upto:
next_linebreak = linebreaks.next()
line += 1
-
- token_tups.append( (template_string[start:end], line) )
- upto = end
-
- while next_linebreak <= upto:
- next_linebreak = linebreaks.next()
- line += 1
- except StopIteration:
- pass
-
- last_bit = template_string[upto:]
- if len(last_bit):
- token_tups.append( (last_bit, line) )
-
- return [ create_token(tok, (filename, line)) for tok, line in token_tups]
-
-def create_token(token_string, source):
- "Convert the given token string into a new Token object and return it"
- if token_string.startswith(VARIABLE_TAG_START):
- return Token(TOKEN_VAR, token_string[len(VARIABLE_TAG_START):-len(VARIABLE_TAG_END)].strip(), source)
- elif token_string.startswith(BLOCK_TAG_START):
- return Token(TOKEN_BLOCK, token_string[len(BLOCK_TAG_START):-len(BLOCK_TAG_END)].strip(), source)
- else:
- return Token(TOKEN_TEXT, token_string, source)
+ except StopIteration:
+ pass
+
+ last_bit = self.template_string[upto:]
+ if len(last_bit):
+ token_tups.append( (last_bit, line) )
+
+ return [ self.create_token(tok, (self.filename, line)) for tok, line in token_tups]
-class Parser:
+ def create_token(self, token_string, source):
+ token = super(DebugLexer, self).create_token(token_string)
+ token.source = source
+ return token
+
+
+class Parser(object):
def __init__(self, tokens):
self.tokens = tokens
- self.command_stack = []
def parse(self, parse_until=[]):
nodelist = NodeList()
while self.tokens:
token = self.next_token()
if token.token_type == TOKEN_TEXT:
- nodelist.append(TextNode(token.contents), token)
+ self.extend_nodelist(nodelist, TextNode(token.contents), token)
elif token.token_type == TOKEN_VAR:
if not token.contents:
- raise TemplateSyntaxError, "Empty variable tag at %s, line %d" % (token.source[0], token.source[1])
- nodelist.append(VariableNode(token.contents), token)
+ self.empty_variable(token)
+ self.extend_nodelist(nodelist, VariableNode(token.contents), token)
elif token.token_type == TOKEN_BLOCK:
if token.contents in parse_until:
# put token back on token list so calling code knows why it terminated
@@ -260,21 +283,44 @@ class Parser:
try:
command = token.contents.split()[0]
except IndexError:
- raise TemplateSyntaxError, "Empty block tag"
+ self.empty_block_tag(token)
+
+ # execute callback function for this tag and append resulting node
+ self.enter_command(command, token);
try:
- # execute callback function for this tag and append resulting node
- self.command_stack.append( (command, token.source) )
- nodelist.append(registered_tags[command](self, token), token)
- self.command_stack.pop()
+ compile_func = registered_tags[command]
except KeyError:
- raise TemplateSyntaxError, "Invalid block tag: '%s' at %s, line %d" % (command, token.source[0], token.source[1])
+ self.invalid_block_tag(token, command)
+
+ self.extend_nodelist(nodelist, compile_func(self, token), token)
+ self.exit_command();
+
if parse_until:
- (command, (file,line)) = self.command_stack.pop()
- msg = "Unclosed tag '%s' starting at %s, line %d. Looking for one of: %s " % \
- (command, file, line, ', '.join(parse_until) )
- raise TemplateSyntaxError, msg
+ self.unclosed_block_tag(token)
+
return nodelist
+ def extend_nodelist(self, nodelist, node, token):
+ nodelist.append(node)
+
+ def enter_command(self, command, token):
+ pass
+
+ def exit_command(self):
+ pass
+
+ def empty_variable(self, token):
+ raise TemplateSyntaxError, "Empty variable tag"
+
+ def empty_block_tag(self, token):
+ raise TemplateSyntaxError, "Empty block tag"
+
+ def invalid_block_tag(self, token, command):
+ raise TemplateSyntaxError, "Invalid block tag: %s" % (command)
+
+ def unclosed_block_tag(self, token):
+ raise TemplateSyntaxError, "Unclosed tags: %s " % ', '.join(parse_until)
+
def next_token(self):
return self.tokens.pop(0)
@@ -284,6 +330,40 @@ class Parser:
def delete_first_token(self):
del self.tokens[0]
+
+class DebugParser(Parser):
+ def __init__(self, lexer):
+ super(DebugParser, self).__init__(lexer)
+ self.command_stack = []
+
+ def enter_command(self, command, token):
+ self.command_stack.append( (command, token.source) )
+
+ def exit_command(self):
+ self.command_stack.pop()
+
+ def format_source(self, source):
+ return "at %s, line %d" % source
+
+ def extend_nodelist(self, nodelist, node, token):
+ node.source = token.source
+ super(DebugParser, self).extend_nodelist(nodelist, node, token)
+
+ def empty_variable(self, token):
+ raise TemplateSyntaxError, "Empty variable tag %s" % self.format_source(token.source)
+
+ def empty_block_tag(self, token):
+ raise TemplateSyntaxError, "Empty block tag %s" % self.format_source(token.source)
+
+ def invalid_block_tag(self, token, command):
+ raise TemplateSyntaxError, "Invalid block tag: '%s' %s" % (command, self.format_source(token.source))
+
+ def unclosed_block_tag(self, token):
+ (command, (file,line)) = self.command_stack.pop()
+ msg = "Unclosed tag '%s' starting at %s, line %d. Looking for one of: %s " % \
+ (command, file, line, ', '.join(parse_until) )
+ raise TemplateSyntaxError, msg
+
class FilterParser:
"""Parse a variable token and its optional filters (all as a single string),
and return a list of tuples of the filter name and arguments.
@@ -484,7 +564,6 @@ class Node:
if hasattr(self, 'nodelist'):
nodes.extend(self.nodelist.get_nodes_by_type(nodetype))
return nodes
-
class NodeList(list):
def render(self, context):
@@ -502,11 +581,6 @@ class NodeList(list):
for node in self:
nodes.extend(node.get_nodes_by_type(nodetype))
return nodes
-
- def append(self, node, token = None):
- if token:
- node.source = token.source
- super(NodeList, self).append(node)
class TextNode(Node):
def __init__(self, s):
diff --git a/django/views/admin/main.py b/django/views/admin/main.py
index fcce2533cc..7c2a66fd25 100644
--- a/django/views/admin/main.py
+++ b/django/views/admin/main.py
@@ -634,7 +634,7 @@ def fill_extra_context(opts, app_label, context, add=False, change=False, show_d
ordered_object_names = ' '.join(['object.%s' % o.pk.name for o in ordered_objects])
extra_context = {
- 'add': add,
+ 'add': add,
'change': change,
'first_form_field_id': first_form_field.get_id(),
'ordered_objects' : ordered_objects,
@@ -804,6 +804,7 @@ def change_stage_new(request, app_label, module_name, object_id):
for rel_opts, rel_field in inline_related_objects:
if rel_opts.order_with_respect_to and rel_opts.order_with_respect_to.rel and rel_opts.order_with_respect_to.rel.to == opts:
+ orig_list = getattr(manipulator.original_object, 'get_%s_list' % opts.get_rel_object_method_name(rel_opts, rel_field))()
form.order_objects.extend(orig_list)
c = Context(request, {
diff --git a/docs/design_philosophies.txt b/docs/design_philosophies.txt
new file mode 100644
index 0000000000..2084c992a5
--- /dev/null
+++ b/docs/design_philosophies.txt
@@ -0,0 +1,244 @@
+===================
+Design philosophies
+===================
+
+This document explains some of the fundamental philosophies Django's developers
+have used in creating the framework. Its goal is to explain the past and guide
+the future.
+
+Overall
+=======
+
+Loose coupling
+--------------
+
+A fundamental goal of Django's stack is `loose coupling and tight cohesion`_.
+The various layers of the framework shouldn't "know" about each other unless
+absolutely necessary.
+
+For example, the template system knows nothing about Web requests, the database
+layer knows nothing about data display and the view system doesn't care which
+template system a programmer uses.
+
+.. _`loose coupling and tight cohesion`: http://c2.com/cgi/wiki?CouplingAndCohesion
+
+Less code
+---------
+
+Django apps should use as little code as possible; they should lack boilerplate.
+Django should take full advantage of Python's dynamic capabilities, such as
+introspection.
+
+Quick development
+-----------------
+
+The point of a Web framework in the 21st century is to make the tedious aspects
+of Web development fast. Django should allow for incredibly quick Web
+development.
+
+Don't repeat yourself (DRY)
+---------------------------
+
+Every distinct concept and/or piece of data should live in one, and only one,
+place. Redundancy is bad. Normalization is good.
+
+The framework, within reason, should deduce as much as possible from as little
+as possible.
+
+Explicit is better than implicit
+--------------------------------
+
+This, a `core Python principle`_, means Django shouldn't do too much "magic."
+Magic shouldn't happen unless there's a really good reason for it.
+
+.. _`core Python principle`: http://www.python.org/doc/Humor.html#zen
+
+Consistency
+-----------
+
+The framework should be consistent at all levels. Consistency applies to
+everything from low-level (the Python coding style used) to high-level (the
+"experience" of using Django).
+
+Models
+======
+
+Explicit is better than implicit
+--------------------------------
+
+Fields shouldn't assume certain behaviors based solely on the name of the
+field. This requires too much knowledge of the system and is prone to errors.
+Instead, behaviors should be based on keyword arguments and, in some cases, on
+the type of the field.
+
+Include all relevant domain logic
+---------------------------------
+
+Models should encapsulate every aspect of an "object," following Martin
+Fowler's `Active Record`_ design pattern.
+
+This is why model-specific admin options are included in the model itself; data
+related to a model should be stored *in* the model.
+
+.. _`Active Record`: http://www.martinfowler.com/eaaCatalog/activeRecord.html
+
+Database API
+============
+
+The core goals of the database API are:
+
+SQL efficiency
+--------------
+
+It should execute SQL statements as few times as possible, and it should
+optimize statements internally.
+
+This is why developers need to call ``save()`` explicitly, rather than the
+framework saving things behind the scenes silently.
+
+This is also why the ``select_related`` argument exists. It's an optional
+performance booster for the common case of selecting "every related object."
+
+Terse, powerful syntax
+----------------------
+
+The database API should allow rich, expressive statements in as little syntax
+as possible. It should not rely on importing other modules or helper objects.
+
+Joins should be performed automatically, behind the scenes, when necessary.
+
+Every object should be able to access every related object, systemwide. This
+access should work both ways.
+
+Option to drop into raw SQL easily, when needed
+-----------------------------------------------
+
+The database API should realize it's a shortcut but not necessarily an
+end-all-be-all. The framework should make it easy to write custom SQL -- entire
+statements, or just custom ``WHERE`` clauses as custom parameters to API calls.
+
+URL design
+==========
+
+Loose coupling
+--------------
+
+URLs in a Django app should not be coupled to the underlying Python code. Tying
+URLs to Python function names is a Bad And Ugly Thing.
+
+Along these lines, the Django URL system should allow URLs for the same app to
+be different in different contexts. For example, one site may put stories at
+``/stories/``, while another may use ``/news/``.
+
+Infinite flexibility
+--------------------
+
+URLs should be as flexible as possible. Any conceivable URL design should be
+allowed.
+
+Encourage best practices
+------------------------
+
+The framework should make it just as easy (or even easier) for a developer to
+design pretty URLs than ugly ones.
+
+File extensions in Web-page URLs should be avoided.
+
+Definitive URLs
+---------------
+
+Technically, ``foo.com/bar`` and ``foo.com/bar/`` are two different URLs, and
+search-engine robots (and some Web traffic-analyzing tools) would treat them as
+separate pages. Django should make an effort to "normalize" URLs so that
+search-engine robots don't get confused.
+
+This is the reasoning behind the ``APPEND_SLASH`` setting.
+
+Template system
+===============
+
+Separate logic from presentation
+--------------------------------
+
+We see a template system as a tool that controls presentation and
+presentation-related logic -- and that's it. The template system shouldn't
+support functionality that goes beyond this basic goal.
+
+If we wanted to put everything in templates, we'd be using PHP. Been there,
+done that, wised up.
+
+Discourage redundancy
+---------------------
+
+The majority of dynamic Web sites use some sort of common sitewide design --
+a common header, footer, navigation bar, etc. The Django template system should
+make it easy to store those elements in a single place, eliminating duplicate
+code.
+
+This is the philosophy behind template inheritance.
+
+Be decoupled from HTML
+----------------------
+
+The template system shouldn't be designed so that it only outputs HTML. It
+should be equally good at generating other text-based formats, or just plain
+text.
+
+Assume designer competence
+--------------------------
+
+The template system shouldn't be designed so that templates necessarily are
+displayed nicely in WYSIWYG editors such as Dreamweaver. That is too severe of
+a limitation and wouldn't allow the syntax to be as nice as it is. Django
+expects template authors are comfortable editing HTML directly.
+
+Treat whitespace obviously
+--------------------------
+
+The template system shouldn't do magic things with whitespace. If a template
+includes whitespace, the system should treat the whitespace as it treats text
+-- just display it.
+
+Don't invent a programming language
+-----------------------------------
+
+The template system intentionally doesn't allow the following:
+
+ * Assignment to variables
+ * Advanced logic
+
+The goal is not to invent a programming language. The goal is to offer just
+enough programming-esque functionality, such as branching and looping, that is
+essential for making presentation-related decisions.
+
+Extensibility
+-------------
+
+The template system should recognize that advanced template authors may want
+to extend its technology.
+
+This is the philosophy behind custom template tags and filters.
+
+Views
+=====
+
+Simplicity
+----------
+
+Writing a view should be as simple as writing a Python function. Developers
+shouldn't have to instantiate a class when a function will do.
+
+Use request objects
+-------------------
+
+Views should have access to a request object -- an object that stores metadata
+about the current request. The object should be passed directly to a view
+function, rather than the view function having to access the request data from
+a global variable. This makes it light, clean and easy to test views by passing
+in "fake" request objects.
+
+Loose coupling
+--------------
+
+A view shouldn't care about which template system the developer uses -- or even
+whether a template system is used at all.
diff --git a/docs/templates.txt b/docs/templates.txt
index a6848a9638..843ed0cbaa 100644
--- a/docs/templates.txt
+++ b/docs/templates.txt
@@ -376,9 +376,9 @@ Built-in tag reference
========================== ================================================
``forloop.counter`` The current iteration of the loop (1-indexed)
``forloop.counter0`` The current iteration of the loop (0-indexed)
- ``forloop.revcounter`` The number of iterations from the end of the
+ ``forloop.revcounter`` The number of iterations from the end of the
loop (1-indexed)
- ``forloop.revcounter0`` The number of iterations from the end of the
+ ``forloop.revcounter0`` The number of iterations from the end of the
loop (0-indexed)
``forloop.first`` True if this is the first time through the loop
``forloop.last`` True if this is the last time through the loop
@@ -569,25 +569,28 @@ Built-in filter reference
-------------------------
``add``
- Adds the arg to the value
+ Adds the arg to the value.
``addslashes``
- Adds slashes - useful for passing strings to JavaScript, for example.
+ Adds slashes. Useful for passing strings to JavaScript, for example.
``capfirst``
- Capitalizes the first character of the value
+ Capitalizes the first character of the value.
``center``
- Centers the value in a field of a given width
+ Centers the value in a field of a given width.
``cut``
- Removes all values of arg from the given string
+ Removes all values of arg from the given string.
``date``
- Formats a date according to the given format (same as the ``now`` tag)
+ Formats a date according to the given format (same as the ``now`` tag).
``default``
- If value is unavailable, use given default
+ If value is unavailable, use given default.
+
+``default_if_none``
+ If value is ``None``, use given default.
``dictsort``
Takes a list of dicts, returns that list sorted by the property given in the
@@ -598,24 +601,24 @@ Built-in filter reference
given in the argument.
``divisibleby``
- Returns true if the value is divisible by the argument
+ Returns true if the value is divisible by the argument.
``escape``
- Escapes a string's HTML
+ Escapes a string's HTML.
``filesizeformat``
Format the value like a 'human-readable' file size (i.e. 13 KB, 4.1 MB, 102
bytes, etc).
``first``
- Returns the first item in a list
+ Returns the first item in a list.
``fix_ampersands``
- Replaces ampersands with ``&`` entities
+ Replaces ampersands with ``&`` entities.
``floatformat``
Displays a floating point number as 34.2 (with one decimal places) - but
- only if there's a point to be displayed
+ only if there's a point to be displayed.
``get_digit``
Given a whole number, returns the requested digit of it, where 1 is the
@@ -624,52 +627,52 @@ Built-in filter reference
or if argument is less than 1). Otherwise, output is always an integer.
``join``
- Joins a list with a string, like Python's ``str.join(list)``
+ Joins a list with a string, like Python's ``str.join(list)``.
``length``
- Returns the length of the value - useful for lists
+ Returns the length of the value. Useful for lists.
``length_is``
- Returns a boolean of whether the value's length is the argument
+ Returns a boolean of whether the value's length is the argument.
``linebreaks``
- Converts newlines into and
s
+ Converts newlines into
and
s.
``linebreaksbr``
- Converts newlines into
s
+ Converts newlines into
s.
``linenumbers``
- Displays text with line numbers
+ Displays text with line numbers.
``ljust``
- Left-aligns the value in a field of a given width
+ Left-aligns the value in a field of a given width.
**Argument:** field size
``lower``
- Converts a string into all lowercase
+ Converts a string into all lowercase.
``make_list``
Returns the value turned into a list. For an integer, it's a list of
digits. For a string, it's a list of characters.
``phone2numeric``
- Takes a phone number and converts it in to its numerical equivalent
+ Takes a phone number and converts it in to its numerical equivalent.
``pluralize``
- Returns 's' if the value is not 1, for '1 vote' vs. '2 votes'
+ Returns 's' if the value is not 1, for '1 vote' vs. '2 votes'.
``pprint``
- A wrapper around pprint.pprint -- for debugging, really
+ A wrapper around pprint.pprint -- for debugging, really.
``random``
- Returns a random item from the list
+ Returns a random item from the list.
``removetags``
- Removes a space separated list of [X]HTML tags from the output
+ Removes a space separated list of [X]HTML tags from the output.
``rjust``
- Right-aligns the value in a field of a given width
+ Right-aligns the value in a field of a given width.
**Argument:** field size
@@ -696,19 +699,19 @@ Built-in filter reference
of Python string formatting
``striptags``
- Strips all [X]HTML tags
+ Strips all [X]HTML tags.
``time``
Formats a time according to the given format (same as the ``now`` tag).
``timesince``
- Formats a date as the time since that date (i.e. "4 days, 6 hours")
+ Formats a date as the time since that date (i.e. "4 days, 6 hours").
``title``
- Converts a string into titlecase
+ Converts a string into titlecase.
``truncatewords``
- Truncates a string after a certain number of words
+ Truncates a string after a certain number of words.
**Argument:** Number of words to truncate after
@@ -733,26 +736,27 @@ Built-in filter reference
``upper``
- Converts a string into all uppercase
+ Converts a string into all uppercase.
``urlencode``
- Escapes a value for use in a URL
+ Escapes a value for use in a URL.
``urlize``
- Converts URLs in plain text into clickable links
+ Converts URLs in plain text into clickable links.
``urlizetrunc``
- Converts URLs into clickable links, truncating URLs to the given character limit
+ Converts URLs into clickable links, truncating URLs to the given character
+ limit.
- **Argument:** Length to truncate URLs to.
+ **Argument:** Length to truncate URLs to
``wordcount``
- Returns the number of words
+ Returns the number of words.
``wordwrap``
- Wraps words at specified line length
+ Wraps words at specified line length.
- **Argument:** number of words to wrap the text at.
+ **Argument:** number of words at which to wrap the text
``yesno``
Given a string mapping values for true, false and (optionally) None,