Commits

Anonymous committed 92eaee3

Clean up unfinished directory, update unfinished/README .

  • Participants
  • Parent commits 1d71c23

Comments (0)

Files changed (9)

File unfinished/DOC_STATUS

-Please mark when modules were documented, meaning all public helpers are
-mentioned and the rst file and docstrings were reviewed.
-
-PACKAGE                  DATE         INITIALS
--------------------      -----------  ----------
-webhelpers
-constants
-containers
-date
-feedgenerator
-html
-html.builder
-html.converters
-html.grid
-html.render
-html.secure_form
-html.tags
-html.tools
-media
-mimehelper
-misc
-number
-paginate
-path
-pylonslib
-pylonslib.flash
-text
-util
-
-Non-essential subpackages
-
-markdown
-textile
-
-

File unfinished/README

-This directory contains functions contributed by James Gardner that need some
-work before they can be included in the release.
+This directory contains helpers that aren't ready to put into the distribution.
+Some work but need API changes or documentation. Others fail their tests or
+don't work at all.
 
-constants.py
+baseN.py
 
-[Already moved to webhelpers.constants.]
+    Convert an int to any base using any alphabet. This can be used to encode
+    base64, or to compress large numbers into fewer digits.
 
-    * Need unit tests.
-    * Need list of US states.
-    * Need docstrings.
+containers.py
 
-fields.py
+    Functions being considered for ``webhelpers.containers``.
 
-    * Needs significant work, not added yet. Left as WebHelpers/james_gardner/fields.py. Recommend 
+disabled_test_pylonslib_minify.py
 
-moving to webhelpers.form when finished.
+    Failing tests for ``webhelpers.pylonslib.minify``.  Need expertise to
+    tell whether these tests are valid or not.
 
-    * form_start() and form_end() are too trivial to add.
-    * radio_group() and checkbox() group are features missing in webhelpers.
-    * " " is not appropriate for styling. Use margin-left style or empty-cells style instead.
-    * Recommend distributing a default stylesheet.
-    * field() is useful as a poor man's form generator. It generates 1-3 <tr>'s depending on the arguments.
-    * All functions need docstrings. Especially field(). What is field's defect and error ars for? How is help different from field_desc and label_desc?
-    * Where are the <form:error> tags for htmlfill?
-    * Need to port all HTML tags to webhelpers.html.HTML, and use literal() where necessary.
-    * options_with_caption: refactor for new select() helper, rename to select_with_caption() or something. Or drop because it's so trivial.
-    * ids_from_options(): too trivial and obscure to add.
-    * value_from_option_id(): what is this function for? Either drop this or refactor it for new select() helper.
-    * Need unit tests. 
+document.py
 
-helpers.py
+    Beginning of a helper to generate a well-formed (X)HTML document with a 
+    minimum of inputs (body, optional head, optional head features).
 
-    * Needs work, did not add yet. Left as WebHelpers/james_gardner/helpers.py.
-    * Recommend adding size_to_human() to webhelpers.text
-    * Recommend inlining divide() into size_to_human because it's trivial and not widely useful.
-    * I'm getting six doctest failures, due to divide() rounding differently than expected, or where the expected string was nothing.
-    * size_to_human may not work as expected with shift to true division. The 'else' part should be rewritten as 'a // b' but I'm not sure if that's legal in Python 2.3. But the test are failing anyway. Consider refactoring with math.modf(), "%d", or "%f". Is it really necessary to divide the same numbers twice? You can divide once, call math.modf() to split out the fractional portion, and if the fraction is zero return the int of the whole, and if it's nonzero return the original result. 
+logging_optparse.py
 
-time.py:
+    An extension of Python's ``optparse`` module that adds standard logging
+    options, including a new trace level and SQLAlchemy engine logging.
+    The plan is to port it to the ``argparse`` module which is in PyPI and
+    will be standard in Python 2.7 and 3.2.
 
-    * What is this module for? Why is it important enough to be a webhelper? 
+multimedia.py
 
+    More multimedia helpers. These have various dependencies and file naming
+    conventions which may not be appropriate for WebHelpers.
 
-Update 2008-04-29
------------------
-helpers.patch addresses some of the issues in helpers.py.
+number_to_human_size.py
 
-test_mail.py is a test suite for webhelpers.mail.  It depends on an SMTP server
-being available, so we're unsure how to integrate it with the standard test
-suite.  
+    The ``number_to_human_size()`` helper from WebHelpers 0.6.4 rails helpers.
+    We're planning to offer a more comprehensive helper but haven't settled on
+    the API yet; 
+    see http://bitbucket.org/bbangert/webhelpers/issue/2/reinstate-number_to_human_size
+    In the meantime, you can copy this version to your application if
+    you need it.
 
-wsgiapp_image.jpg is a file used by test_mail.py.
+opener.py
+
+    A unified opener for regular or compressed files. Leaving as is to see if
+    we can make a better one in Python 3.
+
+sanitize_filename.py
+
+    Sanitize an arbitrary path (as from a file upload) to a safe filename.
+    

File unfinished/config.py

-"""Helpers for configuration files."""
-
-### Can be webhelpers.config or webhelpers.configuration module.
-def ConfigError(Exception):
-    def __init__(self, key, reason, help=None):
-        message = "config variable '%s' %s" % (key, message)
-        if help:
-            message += " (%s)" % help
-        Exception.__init__(self, message)
-
-
-def ConfigLint(object):
-    """Check a configuration dict for errors and set default values."""
-
-    def __init__(self, config):
-        self.config = config
-
-    def convert_int(self, key):
-        """Convert a config variable to integer.  The variable must exist."""
-        try:
-            self.config[key] = int(self.config[key])
-        except ValueError:
-            raise ConfigError(key, "must be an integer: %r" % self.config[key])
-
-    def convert_bool(self, key):
-        """Convert a config variable to boolean.  The variable must exist."""
-        value = self.config[key].strip().lower()
-        if value in ["true", "yes", "on", "y", "t", "1"]:
-            self.config[key] = True
-        elif value in ["false", "no", "off", "n", "f", "0"]:
-            self.config[key] = False
-        else:
-            raise ConfigError(key, "is not true/false: %r" % value)
-
-    def default(self, key, default):
-        self.config.setdefault(key, default)
-
-    def default_int(self, key, default):
-        if key in self.config:
-            self.convert_int(key)
-        else:
-            self.config[key] = default
-
-    def default_bool(self, key, default):
-        if key in self.config:
-            self.convert_bool(key)
-        else:
-            self.config[key] = default
-
-    def require(self, key):
-        if key not in self.config:
-            raise ConfigError(key, "is required")
-
-    def require_int(self, key):
-        self.require(key)
-        self.convert_int(key)
-
-    def require_bool(self, key):
-        self.require_key(key)
-        self.convert_bool(key)
-
-    def require_directory(self, key, create_if_missing=False):
-        """Require a directory to exist, optionally creating it if it doesn't.
-        """
-        self.require(key)
-        dir = self.config[key]
-        if not os.path.exists(dir) and create_if_missing:
-            os.makedirs(dir)
-        if not os.path.isdir(dir):
-            raise ConfigError(key, "is not a directory")
-
-
-
-#### OLDER IMPLEMENTATION ####
-class ConfigurationError(Exception):
-    pass
-
-def validate_config(config, validator, filename=None):
-    """Validate an application's configuration.
-
-    ``config`` 
-        A dict-like object containing configuration values.
-
-    ``validator``
-        A FormEncode `Schema``.  A ``FancyValidator`` is also acceptable if it
-        operates on a dict of values (not on a single value) and raises
-        ``Invalid`` with a dict of error messages (not a single error message).
-
-    ``filename``
-        The configuration file's path if known.  Paste users should pass
-        ``config.__file__`` here.
-
-    This helper depends on Ian Bicking's FormEncode package.
-    """
-    from formencode import Invalid
-    try:
-        return validator.to_python(config)
-    except Invalid, e:
-        if filename:
-            message = "configuration file '%s'" % filename
-        else:
-            message = "the application configuration"
-        message += " has the following errors: "
-        lines = [message]
-        for key, error in sorted(e.error_dict.iteritems()):
-            message = "    %s: %s" % (key, error)
-            lines.append(message)
-        message = "\n".join(lines)
-        raise ConfigurationError(message)
-        
-
-### This is a lower-level alternative to the validation function above, and
-### may produce more appropriate error messages.  In Pylons, these functions
-### should be called by a fix_config() function called in load_environment()
-### in environment.py
-
-class NotGiven(object):
-    pass
-
-def require(config, key):
-    if key not in config:
-        raise KeyError("config variable '%s' is required" % key)
-
-def require_int(config, key, default=NotGiven):
-    want_conversion = True
-    if key not in config:
-        if default is NotGiven:
-            raise KeyError("config variable '%s' is required" % key)
-        value = default
-        want_conversion = False  # Bypass in case default is None.
-    if want_conversion:
-        try:
-            value = int(config[key])
-        except ValueError:
-            raise ValueError("config variable '%s' must be int" % key)
-    config[key] = value
-    return value
-
-def require_bool(config, key, default=NotGiven):
-    from paste.deploy.converters import asbool
-    want_conversion = True
-    if key not in config:
-        if default is NotGiven:
-            raise KeyError("config variable '%s' is required" % key)
-        value = default
-        want_conversion = False  # Bypass in case default is None.
-    if want_conversion:
-        try:
-            value = asbool(config[key])
-        except ValueError:
-            tup = key, config[key]
-            raise ValueError("config option '%s' is not true/false: %r" % tup)
-    config[key] = value
-    return value
-
-def require_dir(config, key, create_if_missing=False):
-    from unipath import FSPath as Path
-    try:
-        dir = config[key]
-    except KeyError:
-        msg = "config option '%s' missing"
-        raise KeyError(msg % key)
-    dir = Path(config[key])
-    if not dir.exists():
-        dir.mkdir(parents=True)
-    if not dir.isdir():
-        msg = ("directory '%s' is missing or not a directory "
-               "(from config option '%s')")
-        tup = dir, key
-        raise OSError(msg % tup)
-

File unfinished/overwrite_error.py

-import os
-
-class OverwriteError(EnvironmentError):
-    """Refusing to overwrite an existing file or directory.
-    
-    Usage:
-    >> raise OverwriteError("OUTPUT")
-    OverwriteError: not overwriting file '/home/me/OUTPUT'
-    >> raise OverwriteError("/tmp", "output")
-    OverwriteError: not overwriting output directory '/tmp'
-
-    It will use the ``os`` module to get the full path and determine
-    whether the existing file is a directory.
-    """
-
-    def __init__(self, filename, what=""):
-        self.filename = filename
-        self.what = what
-        filetype = "file"
-        try:
-            if os.path.isdir(filename):
-                filetype = "directory"
-        except IOError:
-            pass
-        words = []
-        words.append("not overwriting")
-        if what:
-            words.append(what)
-        if filetype:
-            words.append(filetype)
-        words.append("'%s'" % os.path.abspath(filename))
-        msg = " ".join(words)
-        EnvironmentError.__init__(self, msg)
-

File unfinished/path.py

-"""Simple object-oriented path manipulations and file read/write.
-
-The ``Path`` object provides properties and methods for accessing path
-components and joining paths in an object-oriented manner. It also has a few
-filesystem operations for reading and writing a file, and reading symbolic
-links.  Path objects are Unicode subclasses, and can be passed to any ``os``
-function or third-party library that expects a string filename. (If the
-platform can't handle Unicode filenames, Path automatically switches to
-``str`` subclasses.)
-
-``webhelpers.path`` is a subset of Mike Orr's ``Unipath`` package, which itself
-is based on Jason Orendorff's ``path.py``.  The subset provides the most common
-and straightforward path operations for smallish programs that don't want to
-depend on a full path library.  It leaves out most filesystem operations such
-as listing directories and walking directory trees, and other complex code or
-controversial APIs. If you need those, use Python's ``os`` module or install a
-complete path library.  (There are several on PyPI -- search for "path" and
-"filesystem".)
-"""
-
-import os
-
-__all__ = ["Path", "UnsafePathError"]
-
-class UnsafePathError(ValueError):
-    pass
-
-# Use unicode strings if possible
-_base = os.path.supports_unicode_filenames and unicode or str
-
-class Path(_base):
-    """A filesystem path with ``os.path``-like methods."""
-    auto_norm = False
-
-    #### Special Python methods.
-    def __new__(class_, *args, **kw):
-        """Create a path object.
-
-        ``*args`` are one or more string paths, which will be joined using
-        ``os.path.join``. An argument can also be a ``Path`` object or a list
-        of strings, which will be interpolated and joined.
-
-
-        Only one keyword argument is allowed, ``norm``.  If ``norm`` is true
-        or the class attribute ``.auto_norm`` is true, call ``.norm()`` to
-        clean up redundant ".." and ".", double slashes, wrong-direction
-        slashes, etc. On case-insensitive filesystems it also converts
-        uppercase to lower case. Warning: if the filesystem contains symbolic
-        links, normalizing ".." goes to the parent of the symbolic link rather
-        than the parent of the linked-to file. Because normalization can 
-        sometimes produce a different path than expected, it's disabled by
-        default. If you want ``Path`` to always normalize paths, set the 
-        ``.auto_norm`` attribute to True at the beginning of your program.
-        """
-
-        norm = kw.pop("norm", None)
-        if norm is None:
-            norm = class_.auto_norm
-        if kw:
-            kw_str = ", ".join(kw.iterkeys())
-            raise TypeError("unrecognized keyword args: %s" % kw_str)
-        newpath = class_._new_helper(args)
-        if isinstance(newpath, class_):
-            return newpath
-        if norm:
-            newpath = os.path.normpath(newpath)
-            # Can't call .norm() because the path isn't instantiated yet.
-        return _base.__new__(class_, newpath)
-
-    def __add__(self, more):
-        try:
-            resultStr = _base.__add__(self, more)
-        except TypeError:  #Python bug
-            resultStr = NotImplemented
-        if resultStr is NotImplemented:
-            return resultStr
-        return self.__class__(resultStr)
- 
-    @classmethod
-    def _new_helper(class_, args):
-        # If no args, return "." or platform equivalent.
-        if not args:
-            return os.path.curdir
-        # Avoid making duplicate instances of the same immutable path
-        if len(args) == 1 and isinstance(args[0], class_):
-            return args[0]
-        legal_arg_types = (class_, basestring, list, int, long)
-        args = list(args)
-        for i, arg in enumerate(args):
-            if not isinstance(arg, legal_arg_types):
-                m = "arguments must be str, unicode, list, int, long, or %s"
-                raise TypeError(m % class_.__name__)
-            if isinstance(arg, (int, long)):
-                args[i] = str(arg)
-            elif isinstance(arg, class_) and arg.os.path != os.path:
-                arg = getattr(arg, components)()   # Now a list.
-                if arg[0]:
-                    reason = ("must use a relative path when converting "
-                              "from '%s' platform to '%s': %s")
-                    tup = arg.os.path.__name__, os.path.__name__, arg
-                    raise ValueError(reason % tup)
-                # Fall through to convert list of components.
-            if isinstance(arg, list):
-                args[i] = os.path.join(*arg)
-        return os.path.join(*args)
-        
-    def __repr__(self):
-        return '%s(%r)' % (self.__class__.__name__, _base(self))
-
-    def norm(self):
-        __doc__ = os.path.normpath.__doc__
-        return self.__class__(os.path.normpath(self))
-
-    def expand_user(self):
-        __doc__ = os.path.expanduser.__doc__
-        return self.__class__(os.path.expanduser(self))
-    
-    def expand_vars(self):
-        __doc__ = os.path.expandvars.__doc__
-        return self.__class__(os.path.expandvars(self))
-    
-    def expand(self):
-        """Clean up a filename.
-
-        This calls ``.expand_user``, ``.expand_vars``, and ``.norm``
-        on the path.  This is commonly everything needed to clean up a filename
-        read from a configuration file, for example.
-        """
-        newpath = os.path.expanduser(self)
-        newpath = os.path.expandvars(newpath)
-        newpath = os.path.normpath(newpath)
-        return self.__class__(newpath)
-
-    #### Properies: parts of the path.
-
-    @property
-    def parent(self):
-        """The path without the final component; akin to os.path.dirname().
-           Example: Path('/usr/lib/libpython.so').parent => Path('/usr/lib')
-        """
-        return self.__class__(os.path.dirname(self))
-    
-    @property
-    def name(self):
-        """The final component of the path.
-           Example: path('/usr/lib/libpython.so').name => Path('libpython.so')
-        """
-        return self.__class__(os.path.basename(self))
-    
-    @property
-    def stem(self):
-        """Same as path.name but with one file extension stripped off.
-           Example: path('/home/guido/python.tar.gz').stem => Path('python.tar')
-        """
-        return self.__class__(os.path.splitext(self.name)[0])
-    
-    @property
-    def ext(self):
-        """The file extension, for example '.py'."""
-        return self.__class__(os.path.splitext(self)[1])
-
-    #### Methods to extract and add parts to the path.
-
-    def ancestor(self, n):
-        """Remove ``n`` rightmost components from the path.
-
-        Same as using the ``.parent`` attribute ``n`` times.
-
-        Example:
-
-        >>> p = Path("WebHelpers/html/tags.py")
-        >>> p.ancestor(2)
-        Path('WebHelpers')
-        >>> p.parent.parent
-        Path('WebHelpers')
-        """
-        p = self
-        for i in range(n):
-            p = os.path.dirname(p)
-        return self.__class__(p)
-
-    def joinpath(self, *children):
-        """Same as ``os.path.join`` or ``Path(self, \*children)``.
-
-        The children are not checked for special path characters
-        ("/", "..", ".").  See ``.child`` for a "safe" version of this 
-        method.
-        """
-        return self.__class__(self, *children)
-
-    def child(self, *children):
-        """Join paths in a safe manner.
-
-        >>> Path("/tmp", "foo", "bar.txt")
-        Path('/tmp/foo/bar.txt')
-
-        Raise ``UnsafePathError`` if any child contains special path characters
-        ("/", "\\", ".", "..").
-        """
-        for child in children:
-            if os.path.sep in child:
-                msg = "arg '%s' contains path separator '%s'"
-                tup = child, os.path.sep
-                raise UnsafePathError(msg % tup)
-            if os.path.altsep and os.path.altsep in child:
-                msg = "arg '%s' contains alternate path separator '%s'"
-                tup = child, os.path.altsep
-                raise UnsafePathError(msg % tup)
-            if child == os.path.pardir:
-                msg = "arg '%s' is parent directory specifier '%s'"
-                tup = child, os.path.pardir
-                raise UnsafePathError(msg % tup)
-            if child == os.path.curdir:    
-                msg = "arg '%s' is current directory specifier '%s'"
-                tup = child, os.path.curdir
-                raise UnsafePathError(msg % tup)
-        newpath = os.path.join(self, *children)
-        return self.__class__(newpath)
-
-    def norm_case(self):
-        __doc__ = os.path.normcase.__doc__
-        return self.__class__(os.path.normcase(self))
-    
-    def isabsolute(self):
-        """True if the path is absolute.
-           Note that we consider a Windows drive-relative path ("C:foo") 
-           absolute even though ntpath.isabs() considers it relative.
-        """
-        return bool(self.split_root()[0])
-
-
-    ##### CURRENT DIRECTORY ####
-    @classmethod
-    def cwd(class_):
-        """Return the current working directory as a path object."""
-        return class_(os.getcwd())
-
-    #### CALCULATING PATHS ####
-    def absolute(self):
-        """Return the absolute Path, prefixing the current directory if
-           necessary.
-        """
-        return self.__class__(os.path.abspath(self))
-
-    def relpath(self, start=os.curdir):
-        """Make the path relative to ``start`` or the current directory.
-        
-        Available on Python 2.6 and higher only.
-        """
-        try:
-            p = os.path.relpath(self, start)
-        except AttributeError:
-            msg = "Path.relpath() is available only on Python 2.6 and higher"
-            raise TypeError(msg)
-        return self.__class__(p)
-
-    def resolve(self):
-        """Return an equivalent Path that does not contain symbolic links."""
-        return self.__class__(os.path.realpath(self))
-
-    def strip_parents(self):
-        """Remove all directory components from the path in an ultra-safe manner.
-
-        Same as ``p.name``, but also strips Windows-style directory prefixes on
-        Unix and vice-versa. Useful for uploaded files, where the remote
-        filename shouldn't have a directory prefix but may anyway.
-        """
-        p = os.path.basename(self)
-        # On Unix, strip Windows-style directory prefix manually.
-        slash_pos = p.rfind("\\")
-        if slash_pos != -1:
-            p = p[slash_pos+1:]
-        # On Windows, strip Unix-style directory prefix manually.
-        slash_pos = p.rfind("/")
-        if slash_pos != -1:
-            p = p[slash_pos+1:]
-        return self.__class__(p)
-    
-    #### HIGH-LEVEL OPERATIONS ####
-    def read_file(self, mode="rU", encoding=None, errors="strict"):
-        """Read a file and return the contents.
-        
-        ``encoding`` and ``errors`` are used to decode the content to Unicode.
-        If ``encoding`` is not specified, the bytestring is returned as is.
-        """
-        f = open(self, mode)
-        content = f.read()
-        f.close()
-        if encoding:
-            content = content.encode(encoding, errors)
-        return content
-
-    def write_file(self, content, mode="w", encoding=None, errors="strict"):
-        """Write a file.
-        
-        ``encoding`` and ``errors`` are used to encode the content to a 
-        bytestring. If ``encoding`` is not specified, the content will be
-        written as is, which may raise an exception.
-        """
-        if encoding:
-            content = content.encode(encoding, errors)
-        f = open(self, mode)
-        f.write(content)
-        f.close()
-

File unfinished/path.rst

-:mod:`webhelpers.path`
-======================
-
-.. automodule:: webhelpers.path
-
-.. currentmodule:: webhelpers.path
-
-.. autoclass:: Path
-
-   **Constructors:**
-
-   .. automethod:: __new__
-   .. automethod:: cwd
-
-   **Properties:**
-
-   **parent**
-       The path without the final component; akin to os.path.dirname().
-       Example: Path('/usr/lib/libpython.so').parent => Path('/usr/lib')
-       
-
-   **name**
-       The final component of the path.
-       Example: path('/usr/lib/libpython.so').name => Path('libpython.so')
-
-   **stem**
-       Same as path.name but with one file extension stripped off.
-       Example: path('/home/guido/python.tar.gz').stem => Path('python.tar')
-
-   **ext**
-       The file extension, for example '.py'.
-
-   **Absolute paths:**
-
-   .. automethod:: isabsolute
-
-   .. automethod:: absolute
-
-   **Joining paths:**
-
-   .. automethod:: joinpath
-
-   .. automethod:: child
-
-   **Path modification:**
-
-   .. automethod:: ancestor
-
-   .. automethod:: norm
-   
-   .. automethod:: norm_case
-
-   .. automethod:: expand_user
-
-   .. automethod:: expand_vars
-
-   .. automethod:: expand
-
-   **Calculating paths:**
-
-   .. automethod:: relpath
-
-   .. automethod:: resolve
-
-   .. automethod:: strip_parents
-
-   **File reading and writing:**
-
-   .. automethod:: read_file
-
-   .. automethod:: write_file

File unfinished/sanitize_filename.py

     if orig_ext.startswith(".") and ext.startswith(repl):
         ext = "." + ext[len(repl):]
     return stem + ext
-
-# Attachment actions.
-ADD = 'add'
-KEEP = 'keep'
-REPLACE = 'replace'
-DELETE = 'delete'
-
-def attachment_path(orr_id, entry_id, filename):
-    static_dir = config["pylons.paths"]["static_files"]
-    return Path(static_dir, orr_id, entry_id, filename)
-
-class EntriesController(HotlineBaseController):
-    controller_require_perm = "authenticated"
-    # Can't require a more specific perm till we know the orr_id.
-
-    def index(self):
-        partial = bool(request.params.get("partial"))
-        page = self._int_id(request.params.get("page", 1), "Page", 400)
-        p = IndexParamsParser(request.params)
-        c.orr_id = orr_id = p.incident.orr_id
-        self._REQUIRE_PERM("view_incident", orr_id=orr_id)
-        if p.category:
-            if p.category.id == hc.ERD_PRIVATE_CATEGORY:
-                self._REQUIRE_PERM("erd_private", orr_id=orr_id)
-            q = Entry.for_category(orr_id, p.category.id, p.thumbs)
-        else:
-            q = Entry.list(orr_id, p.thumbs)
-            if not self._has_perm("erd_private", orr_id=orr_id):
-                q = q.filter(Entry.category != hc.ERD_PRIVATE_CATEGORY)
-        c.title = "Browse Entries for '%s'" % p.incident.pretty_name
-        c.ht_title = "Browse Entries"
-        c.crumbs = [
-            h.link("Hotline", "hotline"), 
-            h.link("Incident", "incident", id=orr_id), 
-            "Browse Entries"]
-        c.category = p.category
-        c.thumbs = p.thumbs
-        if p.thumbs:
-            recs_per_page = config["thumbs_columns"] * config["thumbs_rows"]
-        else:
-            recs_per_page = config["records_per_page"] 
-        c.entries = Page(q, page, recs_per_page)
-        return render("/hotline/browse_entries.html")
-
-    def show(self, id):
-        self._process_entry_id(id)
-        orr_id = self.incident.orr_id
-        self._REQUIRE_PERM("view_incident", orr_id=orr_id)
-        if self.entry.category == hc.ERD_PRIVATE_CATEGORY:
-            self._REQUIRE_PERM("erd_private", orr_id=orr_id)
-        c.ht_title = "%s: %s" % (self.incident.name, self.entry.title)
-        c.title = self.incident.pretty_name
-        c.crumbs = [
-            h.link("Hotline", "hotline"),
-            h.link("Incident", "incident", id=orr_id),
-            "Entry"]
-        c.entry = self.entry
-        try:
-            c.category_name = literal(hc.CATEGORIES[self.entry.category].label)
-        except KeyError:
-            c.category_name = literal(UNKNOWN)
-        c.can_add_entry = self._has_perm("add_entry", orr_id=orr_id)
-        c.can_modify_entry = self._has_perm("modify_entry", orr_id=orr_id)
-        c.can_delete_entry = self._has_perm("delete_entry", orr_id=orr_id)
-        return render("hotline/entry.html")
-
-    def new(self):
-        """Display the add entry form, which submits to self.create()."""
-        orr_id = self._require_param("orr_id")
-        orr_id = self._int_id(orr_id, "query parameter 'orr_id'", 400)
-        self._REQUIRE_PERM("add_entry", orr_id=orr_id)
-        incident = Incident.get(orr_id)
-        if not incident:
-            abort(400, "Incident #%d does not exist." % orr_id)
-        c.ht_title = "Add entry"
-        c.title = 'Adding entry for "%s"' % incident.pretty_name
-        c.crumbs = [
-            h.link("Hotline", "hotline"),
-            h.link("Incident", "incident", id=incident.orr_id),
-            "Add Entry"]
-        c.action = h.url_for("entries", orr_id=incident.orr_id)
-        c.method = "POST"
-        c.entry = Entry()
-        c.orr_id = orr_id
-        c.category_pairs = self._get_category_pairs(orr_id)
-        c.can_set_public = self._has_perm("set_public", orr_id=orr_id)
-        html = render("hotline/entry_form.html")
-        return htmlfill.render(html, force_defaults=False)
-
-    @validate(schema=EntryForm(), form="new")
-    def create(self):
-        """Create a new entry based on the form input."""
-        log.debug("create() entry input: %s", self.form_result)
-        orr_id = self.form_result["orr_id"]
-        self._REQUIRE_PERM("add_entry", orr_id=orr_id)
-        if self.form_result["category"] == hc.ERD_PRIVATE_CATEGORY:
-            self._REQUIRE_PERM("erd_private", orr_id=orr_id)
-        can_set_public = self._has_perm("set_public", orr_id=orr_id)
-        inc = Incident.get(orr_id)
-        if not inc:
-            abort(400, "Can't add entry to nonexistent incident.")
-        entry = self._create_entry(orr_id, 
-            self.form_result["title"],
-            self.form_result["category"], 
-            self.form_result["content"],
-            can_set_public and self.form_result["is_public"], 
-            )
-        file = self.form_result["attachment"]["file"]
-        if file is not None:
-            self._add_attachment(entry, file.filename, file.file)
-        inc.last_entry_date = entry.entry_date
-        Session.commit()
-        h.flash("Added entry.")
-        self.set_insert_id_for_testing(entry.entry_id)
-        redirect_to("entry", id=entry.entry_id)
-
-    def edit(self, id):
-        """Display the modify entry form, which submits to self.update()."""
-        self._process_entry_id(id)
-        orr_id = self.incident.orr_id
-        self._REQUIRE_PERM("modify_entry", orr_id=orr_id)
-        if self.entry.category == hc.ERD_PRIVATE_CATEGORY:
-            self._REQUIRE_PERM("erd_private", orr_id=orr_id)
-        c.ht_title = "Modify entry"
-        c.title = 'Modifying entry for "%s"' % self.incident.pretty_name
-        c.crumbs = [
-            h.link("Hotline", "hotline"),
-            h.link("Incident", "incident", id=orr_id),
-            h.link("Entry", "entry", id=self.entry_id),
-            "Modify"]
-        c.entry = self.entry
-        c.action = h.url_for("entry", id=self.entry_id)
-        c.method = "PUT"
-        c.entry = self.entry
-        c.orr_id = self.incident.orr_id
-        c.category_pairs = self._get_category_pairs(orr_id)
-        c.can_set_public = self._has_perm("set_public", orr_id=orr_id)
-        html = render("hotline/entry_form.html")
-        return htmlfill.render(html, force_defaults=False)
-
-    @validate(schema=EntryForm(), form="edit")
-    def update(self, id):
-        """Modify the entry based on the form input."""
-        log.debug("update() entry input: %s", self.form_result)
-        self._process_entry_id(id)
-        orr_id = self.incident.orr_id
-        self._REQUIRE_PERM("modify_entry", orr_id=orr_id)
-        if self.form_result["category"] == hc.ERD_PRIVATE_CATEGORY:
-            self._REQUIRE_PERM("erd_private", orr_id=orr_id)
-        self.entry.title = self.form_result["title"]
-        self.entry.category = self.form_result["category"]
-        self.entry.content = self.form_result["content"]
-        if self._has_perm("set_public", orr_id=orr_id):
-            self.entry.is_public = self.form_result["is_public"]
-        action = self.form_result["attachment"]["action"]
-        file = self.form_result["attachment"]["file"]
-        if action in ["delete", "replace"]:
-            self._delete_attachment(self.entry)
-        if action in ["add", "replace"] and file not in [None, ""]:
-            self._add_attachment(self.entry, file.filename, file.file)
-        Session.commit()
-        h.flash("Modified entry.")
-        redirect_to("entry", id=self.entry_id)
-
-    def ask_delete(self, id):
-        """Display the delete entry form, which submits to self.delete()."""
-        self._process_entry_id(id)
-        self._REQUIRE_PERM("delete_entry", orr_id=self.incident.orr_id)
-        if self.entry.category == hc.ERD_PRIVATE_CATEGORY:
-            self._REQUIRE_PERM("erd_private", orr_id=self.incident.orr_id)
-        c.ht_title = "Delete entry"
-        c.title = 'Delete entry "%s"' % self.entry.title
-        c.crumbs = [
-            h.link("Hotline", "hotline"),
-            h.link("Incident", "incident", id=self.incident.orr_id),
-            h.link("Entry", "entry", id=self.entry_id),
-            "Modify"]
-        c.entry = self.entry
-        c.incident = self.incident
-        c.action = h.url_for("entry", id=self.entry.entry_id)
-        c.method = "DELETE"
-        return render("/hotline/delete_entry.html")
-
-    def delete(self, id):
-        """Delete the entry."""
-        self._process_entry_id(id)
-        self._REQUIRE_PERM("delete_entry", orr_id=self.incident.orr_id)
-        if self.entry.category == hc.ERD_PRIVATE_CATEGORY:
-            self._REQUIRE_PERM("erd_private", orr_id=self.incident.orr_id)
-        if request.params.get("submit", "").lower() != "yes":
-            h.flash("Cancelled delete operation.")
-            redirect_to("entry", id=self.entry_id)
-        self._delete_attachment(self.entry)
-        Session.delete(self.entry)
-        Session.commit()
-        h.flash("Deleted entry.")
-        redirect_to("incident", id=self.incident.orr_id)
-
-    def photologger(self, orr_id):
-        self._process_orr_id(orr_id)
-        self._REQUIRE_PERM("add_entry", orr_id=orr_id)
-        c.title = "Import from PhotoLogger"
-        c.crumbs = [
-            h.link("Hotline", "hotline"),
-            h.link("Incident", "incident", id=self.orr_id),
-            "PhotoLogger"]
-        c.action = h.url_for("photologger_submit", orr_id=self.orr_id)
-        c.method = "POST"
-        c.orr_id = self.orr_id
-        return render("/hotline/photologger_form.html")
-
-    @validate(schema=PhotoLoggerForm(), form="photologger")
-    def photologger_submit(self, orr_id):
-        self._process_orr_id(orr_id)
-        orr_id = self.orr_id
-        self._REQUIRE_PERM("add_entry", orr_id=orr_id)
-        now = datetime.datetime.now()
-        wrapper = textwrap.TextWrapper(width=90)
-        upload = self.form_result["file"]
-        try:
-            archive = zipfile.ZipFile(self.form_result["file"].file, "r")
-        except zipfile.BadZipfile:
-            pass   
-        log.debug(archive.namelist())
-        index = archive.read("index.ini")
-        index_fp = StringIO(index)
-        cp = ConfigParser.RawConfigParser()
-        cp.readfp(index_fp)
-        log.debug(cp.sections())
-        count = 0
-        for section in cp.sections():
-            #log.debug("Section %s:", section)
-            section.decode("windows-1252", "xmlcharrefreplace")
-            filename = section  # Just so we don't forget.
-            title = cp.get(section, "Subject").decode("windows-1252", 
-                "xmlcharrefreplace")
-            category = cp.getint(section, "Category")  # @@MO Should validate.
-            content = cp.get(section, "Content").decode("windows-1252", 
-                "xmlcharrefreplace")
-            content = content.replace("Comment: ", "\nComment: ")
-            content = h.wrap_long_lines(content, wrapper)
-            if content.strip():
-                content += "\n\n"
-            content += "Imported from PhotoLogger."
-            is_public = False  # @@MO Should get from form if has permission.
-            entry = self._create_entry(orr_id, title, category, 
-                content, is_public)
-            attachment_content = archive.read(filename)
-            fp = StringIO(attachment_content)
-            self._add_attachment(entry, filename, fp)
-            self.incident.last_entry_date = entry.entry_date
-            count += 1
-        Session.commit()
-        what = h.plural(count, "image", "images")
-        h.flash("Imported %s from PhotoLogger file." % what)
-        redirect_to("incident", id=orr_id)
-
-
-    # UNUSED:
-    #def photologger_results(self):
-    #    # Get results from session.
-    #    try:
-    #        c.successes = session.pop("photologger_successes")
-    #        c.failures = session.pop("photologger_failures")
-    #    except KeyError:
-    #        abort(400, "No photologger upload found.")
-    #    return render("/hotline/photologger_results.html")
-
-
-    ### Private methods
-    def _get_category_pairs(self, orr_id):
-        if 1 or self._has_perm("erd_private", orr_id=orr_id):
-            category_pairs = [(x.id, x.label) for x in hc.CATEGORIES.values()]
-        else:
-            category_pairs = [(x.id, x.label) for x in hc.CATEGORIES.values()
-                if x.id != hc.ERD_PRIVATE_CATEGORY]
-        category_pairs.sort(key=lambda x: x[1])
-        return category_pairs
-
-    def _create_entry(self, orr_id, title, category, content, is_public):
-        """Create an Entry object and attach it to the database.
-
-        Return the new entry object.
-
-        Automatically sets ``entry_id``, ``entry_date``, and ``creator``
-        attributes, and no attachment.  Saves the entry to the database
-        but does not commit the transaction.
-        """
-        entry = Entry()
-        entry.orr_id = orr_id
-        entry.title = title
-        entry.entry_date = datetime.datetime.now()
-        entry.creator = session["user"].username
-        entry.category = category
-        entry.content = content
-        entry.is_public = is_public
-        entry.filename = ""
-        entry.thumb200 = ""
-        entry.doctype = ""
-        entry.size = 0
-        Session.save(entry)
-        Session.flush()  # Write the entry to the db to assign an entry_id.
-        assert entry.entry_id, "Failed to add entry to database."
-        return entry
-
-    def _add_attachment(self, entry, filename, fp):
-        """Save the upload to a file and update the Entry table.
-           Called when adding the attachment, and when replacing it too.
-
-           ``entry``: the entry to attach it to.
-           ``filename``: the user's preferred filename.
-           ``fp``: a file-like object containing the attachment, open for
-               reading and positioned at the beginning.  It will be read
-               and closed.
-
-           No return value.
-        """
-        log.debug("adding attachment for file %s", filename)
-        filename = Path(sanitize_filename(filename))
-        stem = filename.stem
-        ext = filename.ext
-        # PDF thumbnail routine doesn't work right if multiple "." in the
-        # filename
-        if "." in stem:
-            stem = stem.replace(".", "_")
-            filename = Path(stem + ext)
-        # Make sure the filename is short enough for the database fields.
-        max_length = FILENAME_LENGTH - len(ext)
-        if len(stem) > max_length:
-            log.info("Shortening filename %r, length %d", filename, len(filename))
-            filename = stem[:max_length] + ext
-            log.info("Shortened to %r, length %d", filename, len(filename))
-        entry.filename = filename
-        orig = h.attachment_path(entry)
-        log.debug("saving attachment for entry %d to %s", entry.entry_id, orig)
-        if not orig.parent.exists():
-            orig.parent.mkdir(parents=True)
-        self._save_upload(orig, fp)
-        if orig.lower().endswith(".pdf"):
-            thumb = make_pdf_thumbnail(orig, 200)
-        else:
-            thumb = make_thumb(orig, 200)
-            # @@MO: Error making thumbnails from JPG due to palette mode.
-            # I don't know if this is a limitation of PIL or we're not using
-            # PIL right.
-        entry.doctype = ext[1:]
-        entry.size = orig.size()
-        entry.thumb200 = Path(thumb).name if thumb else ""
-
-    def _delete_attachment(self, entry):
-        """Delete the attachment files and update the Entry table.
-           Called when deleting the attachment, and when replacing it too.
-
-           `entry`: the entry to delete the attachment from.
-           No return value.
-        """
-        if not entry.filename:
-            return
-        dir = h.attachment_path(entry).parent
-        log.debug("deleting attachment diectory '%s'", dir)
-        if dir.isdir():
-            try:
-                dir.rmtree()
-            except Exception, e:
-                msg = "caught exception deleting entry directory '%s': %s: %s"
-                log.warn(msg, dir, e.__class__.__name__, e)
-        elif dir.isfile():
-            msg = "tried to delete entry directory '%s' but it's a file"
-            log.warn(msg, dir)
-        # Try to delete incident directory too.
-        try:
-            dir.parent.rmdir()
-        except OSError:
-            pass   # Assume it's not empty; it probably contains other entries.
-        # Else the directory doesn't exist; ignore it.
-        entry.filename = ""
-        entry.thumb200 = ""
-        entry.doctype = ""
-        entry.size = 0
-
-        
-
-#### Helper utilities ####
-class IndexParamsParser(object):
-    """Parse the query parameters used by EntryController.index.
-
-       Instance variables:
-       .incident:  model.hotline.Incident based on 'orr_id' param.
-       .category:  hazpy.hotline.Category or None.
-       .thumbs:    boolean. Show thumbnails view instead of list view.
-    """
-    def __init__(self, params):
-        self.incident = self._get_incident(params)
-        self.category = self._get_category(params)
-        self.thumbs = self._get_thumbs(params, self.category)
-
-    def _get_incident(self, params):
-        """Fetch an incident via 'orr_id' query parameter.
-           Abort if parameter is absent or refers to nonexistent incident.
-        """
-        try:
-            orr_id = int(params["orr_id"])
-        except KeyError:
-            abort(400, "query parameter 'orr_id' required")
-        except ValueError:
-            abort(400, "query parameter 'orr_id' must be numeric")
-        incident = Incident.get(orr_id)
-        if not incident:
-            abort(400, "incident #%d does not exist" % orr_id)
-        return incident
-
-    def _get_category(self, params):
-        """Fetch a Category via 'category' query parameter.
-           Return None if no category was specified.
-        """
-        catnum = params.get("category")
-        if not catnum:
-            return None  # User did not specify category.
-        try:
-            catnum = int(catnum)
-        except ValueError:
-            abort(400, "query parameter 'category' must be numeric")
-        try:
-            category = hc.CATEGORIES[catnum]
-        except KeyError:
-            abort(400, "no such category '%s'" % category) # Safe, numeric.
-        return category
-
-    def _get_thumbs(self, params, category):
-        """Return the boolean 'thumbs' param, which tells whether to display
-           the index as a list (false) or thumbnails (true).  If param is
-           missing, use the category's default view.  If the category is None,
-           return False.
-        """
-        if "thumbs" in params:
-            return bool(params["thumbs"])
-        if category:
-            return category.default_thumb
-        return False
-

File unfinished/test_path.py

-#!/usr/bin/env python
-"""Unit tests for unipath.py and unipath_purist.py
-
-Environment variables:
-    DUMP : List the contents of test direcories after each test.
-    NO_CLEANUP : Don't delete test directories.
-(These are not command-line args due to the difficulty of merging my args
-with unittest's.)
-
-IMPORTANT: Tests may not assume what the current directory is because the tests
-may have been started from anywhere, and some tests chdir to the temprorary
-test directory which is then deleted.
-"""
-import os
-import tempfile
-import time
-import unittest
-import sys
-
-from nose.tools import eq_, assert_raises as r
-
-# Package imports
-from webhelpers.path import Path
-
-cleanup = not bool(os.environ.get("NO_CLEANUP"))
-dump = bool(os.environ.get("DUMP"))
-
-class TestPathConstructor(object):
-    def test_args(self):
-        eq_(str(Path()), Path.curdir)
-        eq_(str(Path("foo/bar.py")), "foo/bar.py")
-        eq_(str(Path("foo", "bar.py")), "foo/bar.py")
-        eq_(str(Path("foo", "bar", "baz.py")), "foo/bar/baz.py")
-        eq_(str(Path("foo", Path("bar", "baz.py"))), "foo/bar/baz.py")
-        eq_(str(Path("foo", ["", "bar", "baz.py"])), "foo/bar/baz.py")
-        eq_(str(Path("")), "")
-        eq_(str(Path()), ".")
-        eq_(str(Path("a", 1)), "a/1")
-
-    def test_norm(self):
-        eq_(Path("a//b/../c").norm(), "a/c")
-        eq_(Path("a/./b").norm(), "a/b")
-        eq_(Path("a/./b", norm=True), "a/b")
-        eq_(Path("a/./b", norm=False), "a/./b")
-        class AutoNormPath(Path):
-            auto_norm = True
-        eq_(AutoNormPath("a/./b"), "a/b")
-        eq_(AutoNormPath("a/./b", norm=True), "a/b")
-        eq_(AutoNormPath("a/./b", norm=False), "a/./b")
-
-
-class TestPath(object):
-    def test_repr(self):
-        eq_(repr(Path("la_la_la")), "Path('la_la_la')")
-
-    # Not testing expand_user, expand_vars, or expand: too dependent on the
-    # OS environment.
-
-    def test_properties(self):
-        p = Path("/first/second/third.jpg")
-        eq_(p.parent, "/first/second")
-        eq_(p.name, "third.jpg")
-        eq_(p.ext, ".jpg")
-        eq_(p.stem, "third")
-
-    def test_properties2(self):
-        p = Path("/usr/lib/python2.5/gopherlib.py")
-        eq_(p.parent, Path("/usr/lib/python2.5"))
-        eq_(p.name, Path("gopherlib.py"))
-        eq_(p.ext, ".py")
-        eq_(p.stem, Path("gopherlib"))
-        q = Path(p.parent, p.stem + p.ext) 
-        eq_(q, p)
-
-    def test_split_root(self):
-        eq_(Path("foo/bar.py").split_root(), ("", "foo/bar.py"))
-        eq_(Path("/foo/bar.py").split_root(), ("/", "foo/bar.py"))
-
-    def test_split_root_vs_isabsolute(self):
-        self.failIf(Path("a/b/c").isabsolute())
-        self.failIf(Path("a/b/c").split_root()[0])
-        self.assert_(Path("/a/b/c").isabsolute())
-        self.assert_(Path("/a/b/c").split_root()[0])
-        
-
-    def test_components(self):
-        P = Path
-        eq_(P("a").components(), [P(""), P("a")])
-        eq_(P("a/b/c").components(), [P(""), P("a"), P("b"), P("c")])
-        eq_(P("/a/b/c").components(), [P("/"), P("a"), P("b"), P("c")])
-
-    def test_joinpath(self):
-        P = Path
-        eq_(P("foo/bar", "baz", "fred"), "foo/bar/baz/fred")
-        eq_(P("foo/bar", "baz/fred"), "foo/bar/baz/fred")
-        eq_(P("foo/bar", "..", "fred"), "foo/bar/../fred")
-        eq_(P("foo/bar", ".", "fred"), "foo/bar/./fred")
-
-
-    def test_child(self):
-        Path("foo/bar").child("baz")
-        r(UnsafePathError, Path("foo/bar").child, "baz/fred")
-        r(UnsafePathError, Path("foo/bar").child, "..", "baz")
-        r(UnsafePathError, Path("foo/bar").child, ".", "baz")
-
-
-class FilesystemTest(object):
-    TEST_HIERARCHY = {
-        "a_file":  "Nothing important.",
-        "animals": {
-            "elephant":  "large",
-            "gonzo":  "unique",
-            "mouse":  "small"},
-        "images": {
-            "image1.gif": "",
-            "image2.jpg": "",
-            "image3.png": ""},
-        "swedish": {
-            "chef": {
-                "bork": {
-                    "bork": "bork!"}}},
-        }
-
-    def setUp(self):
-        self.d = d = Path(tempfile.mkdtemp())
-        dict2dir(d, self.TEST_HIERARCHY)
-        self.a_file = Path(d, "a_file")
-        self.animals = Path(d, "animals")
-        self.images = Path(d, "images")
-        self.chef = Path(d, "swedish", "chef", "bork", "bork")
-        if hasattr(self.d, "symlink"):
-            self.link_to_chef_file = Path(d, "link_to_chef_file")
-            self.link_to_images_dir = Path(d, "link_to_images_dir")
-            self.chef.symlink(self.link_to_chef_file)
-            self.images.symlink(self.link_to_images_dir)
-            self.dead_link = self.d.child("dead_link")
-            self.dead_link.write_link("nowhere")
-        self.missing = Path(d, "MISSING")
-        self.d.chdir()
-
-    def tearDown(self):
-        d = self.d
-        d.parent.chdir()  # Always need a valid curdir to avoid OSErrors.
-        if dump:
-            dump_path(d)
-        if cleanup:
-            d.rmtree()
-            if d.exists():
-                raise AssertionError("unable to delete temp dir %s" % d)
-        else:
-            print "Not deleting test directory", d
-
-
-class TestCalculatingPaths(FilesystemTest):
-    def test_inheritance(self):
-        assert Path.cwd().name    # Can we access the property?
-
-    def test_cwd(self):
-        eq_(str(Path.cwd()), os.getcwd())
-
-    def test_chdir_absolute_relative(self):
-        save_dir = Path.cwd()
-        self.d.chdir()
-        eq_(Path.cwd(), self.d)
-        eq_(Path("swedish").absolute(), Path(self.d, "swedish"))
-        save_dir.chdir()
-        eq_(Path.cwd(), save_dir)
-
-    def test_chef(self):
-        p = Path(self.d, "swedish", "chef", "bork", "bork")
-        eq_(p.read_file(), "bork!")
-
-    
-    def test_absolute(self):
-        p1 = Path("images").absolute()
-        p2 = self.d.child("images")
-        eq_(p1, p2)
-
-    def test_relative(self):
-        p = self.d.child("images").relative()
-        eq_(p, "images")
-
-    def test_resolve(self):
-        p1 = Path(self.link_to_images_dir, "image3.png")
-        p2 = p1.resolve()
-        eq_(p1.components()[-2:], ["link_to_images_dir", "image3.png"])
-        eq_(p2.components()[-2:], ["images", "image3.png"])
-        assert p1.exists()
-        assert p2.exists()
-        assert p1.same_file(p2)
-        assert p2.same_file(p1)
-
-
-def TestRelPathTo(FilesystemTest):
-    def test1(self):
-        p1 = Path("animals", "elephant")
-        p2 = Path("animals", "mouse")
-        eq_(p1.rel_path_to(p2), Path("mouse"))
-        
-    def test2(self):
-        p1 = Path("animals", "elephant")
-        p2 = Path("images", "image1.gif")
-        eq_(p1.rel_path_to(p2), Path(os.path.pardir, "images", "image1.gif"))
-        
-    def test3(self):
-        p1 = Path("animals", "elephant")
-        eq_(p1.rel_path_to(self.d), Path(os.path.pardir))
-        
-    def test3(self):
-        p1 = Path("swedish", "chef")
-        eq_(p1.rel_path_to(self.d), Path(os.path.pardir, os.path.pardir))
-        
-
-class TestHighLevel(FilesystemTest):
-    def test_read_file(self):
-        eq_(self.chef.read_file(), "bork!")
-
-    # .write_file and .rmtree tested in .setUp.
-
-

File unfinished/unit_conversion.py

-"""
-There are several unit conversion modules available for Python.  However, most
-are large and complex.  A very small simple set of converters may be
-appropriate for WebHelpers.  Here are some alternatives.
-
-Chris Barker wrote some conversion tables (see module body) and recommends a
-general convert function::
-
-    convert(type, unit, to_unit, value)
-
-``type`` is a unit type such as "Length", "Volume", "Temperature", "Mass",
-etc.  ``unit`` and ``to_unit`` are two units of that type.  ``value`` is the
-number you wish to convert.  The result is the value converted to ``to_unit``.
-
-The Python Cookbook has a recipe called "Unit-safe measured quantities" 
-(http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/270589)
-by George Sakkis that does something similar.  Values are created in a more
-conventional manner.  This example shows creating two length values, adding
-them, comparing them, displaying them in a particular unit, and changing the
-default display unit::
-
-    l1 = Length(12.3,'cm')
-    l2 = Length(50,'mm')
-    Length.setDefaultUnit("in")
-    print l1+l2             # "6.811024 in"
-    print 3*l2.value("m")   # "0.15"
-    print l1>l2             # "True"
-
-Sakkis notes that values stored as plain floats tend to get confused when
-multiple units or external modules are involved.  However, they can't be stored
-in a numeric database field.  This implementation also doesn't handle aliases
-(different names for the same unit), and has concrete classes only for Length
-and Temperature.  It may be worthwhile to refactor this to use a converter like
-Barker's, and then plug Barker's tables and aliases into it.  This would allow
-users to choose between plain floats and Sakkis objects as desired, and to also
-make canned converters as partials.
-
-The Unum project is even more sophistocated, with a large collection of units
-that can be used as Python operands.  For instance, ``3 * M/S`` creates a "3
-meters per second" object.  Compatible values can be added, and plugged into
-Numeric matrices.  In spite of this it's pure Python and has no nonstandard
-dependencies.  However, it may be slower than other implementations due to the
-overhead of the magic.  And it has a high learning curve, perhaps too high for
-non-mathematical users with simple needs.
-
-    * Unum project home:  http://sourceforge.net/projects/unum/
-    * Tutorial: http://home.scarlet.be/be052320/Unum_tutorial.html
-    * FAQ (including limitations):  http://home.scarlet.be/be052320/faq.html
-"""
-
-### Chris Barker's conversion tables.
-### (Additional scientific tables are available.)
-
-ConvertDataUnits = {
-# All lengths in terms of meter
-
-"Length" : {"meter"      : (1.0,["m","meters","metre"]),
-            "centimeter" : (0.01,["cm", "centimeters"]),
-            "millimeter"  : (0.001,["mm","millimeters"]),
-            "micron"  : (0.000001,["microns"]),
-            "kilometer"  : (1000.0,["km","kilometers"]),
-            "foot"        : (0.3048,["ft", "feet"]),
-            "inch"      : (0.0254,["in","inches"]),
-            "yard"       : (0.9144,[ "yrd","yards"]),
-            "mile"       : (1609.344,["mi", "miles"]),
-            "nautical mile" : (1852.0,["nm","nauticalmiles"]),
-            "fathom"  : (1.8288,["fthm", "fathoms"]),
-            "latitude degree": (111120.0,["latitudedegrees"]),
-            "latitude minute": (1852.0,["latitudeminutes"])
-            },
-
-# All Areas in terms of square meter
-"Area" : {"square meter"  : (1.0,["m^2","sq m","squaremeter"]),
-          "square centimeter": (.0001,["cm^2","sq cm"]),
-          "square kilometer"  : (1e6,["km^2","sq km","squarekilometer"]),
-          "acre"  : (4046.8564,["acres"]),
-          "square mile"  : (2589988.1,["sq miles","squaremile"]),
-          "square yard"  : (0.83612736,["sq yards","squareyards"]),
-          "square foot"  : (0.09290304,["ft^2", "sq foot","square feet"]),
-          "square inch"  : (0.00064516,["in^2", "sq inch","square inches"]),
-          "hectar"  : (10000.0,["hectares"]),
-          },
-
-# All volumes in terms of cubic meter
-"Volume" : {"cubic meter"  : (1.0,["m^3","cu m","cubic meters"]),
-            "cubic centimeter"  : (1e-6,["cm^3","cu cm"]),
-            "barrels (petroleum)" : (.1589873,["bbl","barrels","barrel","bbls",]),
-            "liter"        : (1e-3,["l","liters"]),
-            "gallon"       : (0.0037854118, ["gal","gallons","gallon","usgal"]),
-            "gallon (UK)"  : (0.004546090, ["ukgal","gallons(uk)"]),
-            "million US gallons"  : (3785.4118, ["milliongallons","milgal"]),
-            "cubic foot"    : (0.028316847, ["ft^3","cu feet","cubicfeet"]),
-            "cubic inch"    : (16.387064e-6, ["in^3","cu inch","cubicinches"]),
-            "cubic yard"    : (.76455486, ["yd^3","cu yard","cubicyard","cubicyards"]),
-            "fluid oz"      : (2.9573530e-5, ["oz","ounces(fluid)", "fluid oz"]),
-            "fluid oz (UK)" : (2.841306e-5, ["ukoz", "fluid oz(uk)"]),
-            },
-
-# All Temperature units in K (multiply by, add)
-"Temperature" : {"Kelvin"  : ((1.0, 0.0),["K","degrees k","degrees k","degrees kelvin","degree kelvin","deg k"]),
-                 "centigrade"     : ((1.0, 273.16),["C","degrees c","degrees celsius","degree celsius","deg c"]),
-                 "farenheight"  : ((0.55555555555555558, (273.16*9/5 - 32) ),["F","degrees f","degree f","degrees farenheight","deg f"]),
-                 },
-
-# All Mass units in Kg (weight is taken to be mass at standard g)
-"Mass" : {"kilograms"  : (1.0,["kg","kilogram"]),
-          "pound"     : (0.45359237,["lb","pounds","lbs"]),
-          "gram"  : (.001,["g","grams"]),
-          "ton"   : (907.18474, ["tons","uston"]),
-          "metric ton" : (1000.0, ["tonnes","metric tons"]),
-          "slug"       : (14.5939, ["slugs"]),
-          "ounce"       : (.028349523, ["oz","ounces"]),
-          "ton(UK)"       : (1016.0469, ["ukton","long ton"]),
-          },
-
-# All Time In second
-"Time" : {"second"  : (1.0,["sec","seconds"]),
-          "minute"  : (60.0,["min","minutes"]),
-          "hour"    : (3600.0,["hr","hours","hrs"]),
-          "day"     : (86400.0,["day","days"]),
-          },
-# All Velocities in meter per second
-"Velocity" : {"meter per second"  : (1.0,["m/s","meters per second","mps"]),
-              "centimeter per second"  : (.01,["cm/s"]),
-              "kilometer per hour"  : (0.277777,["km/h", "km/hr"]),
-              "knot"  : (0.514444,["knots","kts"]),
-              "mile per hour"  : (0.44704,["mph","miles per hour"]),
-              "foot per second"  : (0.3048,["ft/s", "feet per second", "feet/s"]),
-              },
-}
-
-
-# Aliases should be stored in a normalized manner to prevent spelling
-# variations from causing lookup failures.  Barker uses the following
-# normalizer:
-#
-#    def normalize_unit(unit):
-#        return "".join(unit.lower().split())
-#
-# Unit arguments are then filtered by this before lookup.