WebHelpers-1.3/ 0000775 0001750 0001750 00000000000 11542603374 013006 5 ustar sluggo sluggo WebHelpers-1.3/setup.py 0000664 0001750 0001750 00000003576 11542603250 014524 0 ustar sluggo sluggo try:
from setuptools import setup, find_packages
except ImportError:
from ez_setup import use_setuptools
use_setuptools()
from setuptools import setup, find_packages
version = '1.3'
setup(
name="WebHelpers",
version=version,
description='Web Helpers',
long_description="""
Web Helpers is a library of helper functions intended to make writing
web applications easier. It's the standard function library for
Pylons and TurboGears 2, but can be used with any web framework. It also
contains a large number of functions not specific to the web, including text
processing, number formatting, date calculations, container objects, etc.
Version 1.3 fixes a performance regression in 1.2 regarding paginate with
SQLAlchemy.
WebHelpers itself depends only on MarkupSafe, but certain helpers depend on
third-party packages as described in the docs.
The development version of WebHelpers is at
http://bitbucket.org/bbangert/webhelpers (Mercurial)
""",
author='Mike Orr, Ben Bangert, Phil Jenvey',
author_email='sluggoster@gmail.com, ben@groovie.org, pjenvey@groovie.org',
url='http://webhelpers.groovie.org/',
packages=find_packages(exclude=['ez_setup']),
zip_safe=False,
include_package_data=True,
install_requires=[
'MarkupSafe>=0.9.2',
],
tests_require=[
'Nose',
'Routes',
'WebOb',
],
test_suite='nose.collector',
classifiers=["Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"License :: OSI Approved :: BSD License",
"Programming Language :: Python",
"Topic :: Internet :: WWW/HTTP :: Dynamic Content",
"Topic :: Software Development :: Libraries :: Python Modules",
],
entry_points="""
[buildutils.optional_commands]
compress_resources = webhelpers.commands
""",
)
WebHelpers-1.3/PKG-INFO 0000664 0001750 0001750 00000002562 11542603374 014110 0 ustar sluggo sluggo Metadata-Version: 1.0
Name: WebHelpers
Version: 1.3
Summary: Web Helpers
Home-page: http://webhelpers.groovie.org/
Author: Mike Orr, Ben Bangert, Phil Jenvey
Author-email: sluggoster@gmail.com, ben@groovie.org, pjenvey@groovie.org
License: UNKNOWN
Description:
Web Helpers is a library of helper functions intended to make writing
web applications easier. It's the standard function library for
Pylons and TurboGears 2, but can be used with any web framework. It also
contains a large number of functions not specific to the web, including text
processing, number formatting, date calculations, container objects, etc.
Version 1.3 fixes a performance regression in 1.2 regarding paginate with
SQLAlchemy.
WebHelpers itself depends only on MarkupSafe, but certain helpers depend on
third-party packages as described in the docs.
The development version of WebHelpers is at
http://bitbucket.org/bbangert/webhelpers (Mercurial)
Platform: UNKNOWN
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: BSD License
Classifier: Programming Language :: Python
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
Classifier: Topic :: Software Development :: Libraries :: Python Modules
WebHelpers-1.3/WebHelpers.egg-info/ 0000775 0001750 0001750 00000000000 11542603374 016540 5 ustar sluggo sluggo WebHelpers-1.3/WebHelpers.egg-info/PKG-INFO 0000664 0001750 0001750 00000002562 11542603374 017642 0 ustar sluggo sluggo Metadata-Version: 1.0
Name: WebHelpers
Version: 1.3
Summary: Web Helpers
Home-page: http://webhelpers.groovie.org/
Author: Mike Orr, Ben Bangert, Phil Jenvey
Author-email: sluggoster@gmail.com, ben@groovie.org, pjenvey@groovie.org
License: UNKNOWN
Description:
Web Helpers is a library of helper functions intended to make writing
web applications easier. It's the standard function library for
Pylons and TurboGears 2, but can be used with any web framework. It also
contains a large number of functions not specific to the web, including text
processing, number formatting, date calculations, container objects, etc.
Version 1.3 fixes a performance regression in 1.2 regarding paginate with
SQLAlchemy.
WebHelpers itself depends only on MarkupSafe, but certain helpers depend on
third-party packages as described in the docs.
The development version of WebHelpers is at
http://bitbucket.org/bbangert/webhelpers (Mercurial)
Platform: UNKNOWN
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: BSD License
Classifier: Programming Language :: Python
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
Classifier: Topic :: Software Development :: Libraries :: Python Modules
WebHelpers-1.3/WebHelpers.egg-info/entry_points.txt 0000664 0001750 0001750 00000000125 11542603374 022034 0 ustar sluggo sluggo
[buildutils.optional_commands]
compress_resources = webhelpers.commands
WebHelpers-1.3/WebHelpers.egg-info/dependency_links.txt 0000664 0001750 0001750 00000000001 11542603374 022606 0 ustar sluggo sluggo
WebHelpers-1.3/WebHelpers.egg-info/top_level.txt 0000664 0001750 0001750 00000000013 11542603374 021264 0 ustar sluggo sluggo webhelpers
WebHelpers-1.3/WebHelpers.egg-info/SOURCES.txt 0000664 0001750 0001750 00000011121 11542603374 020420 0 ustar sluggo sluggo CHANGELOG
LICENSE
MANIFEST.in
README.txt
TODO
requirements.txt
setup.cfg
setup.py
WebHelpers.egg-info/PKG-INFO
WebHelpers.egg-info/SOURCES.txt
WebHelpers.egg-info/dependency_links.txt
WebHelpers.egg-info/entry_points.txt
WebHelpers.egg-info/not-zip-safe
WebHelpers.egg-info/requires.txt
WebHelpers.egg-info/top_level.txt
docs/Makefile
docs/build
docs/changelog.rst
docs/conf.py
docs/contents.rst
docs/development.rst
docs/history.rst
docs/index.rst
docs/third_party.rst
docs/todo.rst
docs/whats_new.rst
docs/_static/webhelpers-logo.png
docs/_themes/.gitignore
docs/_themes/CONTRIBUTORS.txt
docs/_themes/LICENSE.txt
docs/_themes/README.rst
docs/_themes/pylons_theme_support.py
docs/_themes/.git/HEAD
docs/_themes/.git/config
docs/_themes/.git/description
docs/_themes/.git/index
docs/_themes/.git/packed-refs
docs/_themes/.git/hooks/applypatch-msg.sample
docs/_themes/.git/hooks/commit-msg.sample
docs/_themes/.git/hooks/post-commit.sample
docs/_themes/.git/hooks/post-receive.sample
docs/_themes/.git/hooks/post-update.sample
docs/_themes/.git/hooks/pre-applypatch.sample
docs/_themes/.git/hooks/pre-commit.sample
docs/_themes/.git/hooks/pre-rebase.sample
docs/_themes/.git/hooks/prepare-commit-msg.sample
docs/_themes/.git/hooks/update.sample
docs/_themes/.git/info/exclude
docs/_themes/.git/logs/HEAD
docs/_themes/.git/logs/refs/heads/master
docs/_themes/.git/objects/pack/pack-7223556f5a85a1e44a4a475abe01be5e042c09f5.idx
docs/_themes/.git/objects/pack/pack-7223556f5a85a1e44a4a475abe01be5e042c09f5.pack
docs/_themes/.git/refs/heads/master
docs/_themes/.git/refs/remotes/origin/HEAD
docs/_themes/pylons/layout.html
docs/_themes/pylons/theme.conf
docs/_themes/pylons/static/dialog-note.png
docs/_themes/pylons/static/dialog-seealso.png
docs/_themes/pylons/static/dialog-topic.png
docs/_themes/pylons/static/dialog-warning.png
docs/_themes/pylons/static/epub.css
docs/_themes/pylons/static/footerbg.png
docs/_themes/pylons/static/headerbg.png
docs/_themes/pylons/static/ie6.css
docs/_themes/pylons/static/middlebg.png
docs/_themes/pylons/static/pylons-latex.png
docs/_themes/pylons/static/pylons-small.png
docs/_themes/pylons/static/pylons.css_t
docs/_themes/pylons/static/pylons.ico
docs/_themes/pylons/static/pylons.png
docs/_themes/pylons/static/transparent.gif
docs/_themes/pylonsfw/theme.conf
docs/_themes/pylonsfw/static/pylons.ico
docs/_themes/pylonsfw/static/pylonsfw-small.png
docs/_themes/pyramid/theme.conf
docs/_themes/pyramid/static/pyramid-latex.png
docs/_themes/pyramid/static/pyramid-small.png
docs/_themes/pyramid/static/pyramid.ico
docs/_themes/pyramid/static/pyramid.png
docs/modules/constants.rst
docs/modules/containers.rst
docs/modules/date.rst
docs/modules/feedgenerator.rst
docs/modules/markdown.rst
docs/modules/media.rst
docs/modules/mimehelper.rst
docs/modules/misc.rst
docs/modules/number.rst
docs/modules/paginate.rst
docs/modules/text.rst
docs/modules/textile.rst
docs/modules/util.rst
docs/modules/html/__init__.rst
docs/modules/html/builder.rst
docs/modules/html/converters.rst
docs/modules/html/grid.rst
docs/modules/html/tags.rst
docs/modules/html/tools.rst
docs/modules/pylonslib/__init__.rst
docs/modules/pylonslib/flash.rst
docs/modules/pylonslib/grid.rst
docs/modules/pylonslib/minify.rst
docs/modules/pylonslib/secure_form.rst
tests/test_containers.py
tests/test_converters.py
tests/test_date.py
tests/test_escapes.py
tests/test_feedgenerator.py
tests/test_html.py
tests/test_mimetypes.py
tests/test_misc.py
tests/test_modeltags.py
tests/test_number.py
tests/test_paginate.py
tests/test_pylonslib_flash.py
tests/test_tags.py
tests/test_text.py
tests/test_tools.py
tests/util.py
unfinished/README
unfinished/baseN.py
unfinished/containers.py
unfinished/disabled_test_pylonslib_minify.py
unfinished/document.py
unfinished/logging_optparse.py
unfinished/multimedia.py
unfinished/number_to_human_size.py
unfinished/opener.py
unfinished/sanitize_filename.py
webhelpers/__init__.py
webhelpers/constants.py
webhelpers/containers.py
webhelpers/date.py
webhelpers/feedgenerator.py
webhelpers/markdown.py
webhelpers/media.py
webhelpers/mimehelper.py
webhelpers/misc.py
webhelpers/number.py
webhelpers/paginate.py
webhelpers/text.py
webhelpers/textile.py
webhelpers/util.py
webhelpers/html/__init__.py
webhelpers/html/builder.py
webhelpers/html/converters.py
webhelpers/html/grid.py
webhelpers/html/grid_demo.py
webhelpers/html/render.py
webhelpers/html/tags.py
webhelpers/html/tools.py
webhelpers/public/stylesheets/grid.css
webhelpers/public/stylesheets/webhelpers.css
webhelpers/pylonslib/__init__.py
webhelpers/pylonslib/_jsmin.py
webhelpers/pylonslib/flash.py
webhelpers/pylonslib/grid.py
webhelpers/pylonslib/minify.py
webhelpers/pylonslib/secure_form.py WebHelpers-1.3/WebHelpers.egg-info/requires.txt 0000664 0001750 0001750 00000000021 11542603374 021131 0 ustar sluggo sluggo MarkupSafe>=0.9.2 WebHelpers-1.3/WebHelpers.egg-info/not-zip-safe 0000664 0001750 0001750 00000000001 11350576011 020760 0 ustar sluggo sluggo
WebHelpers-1.3/setup.cfg 0000644 0001750 0001750 00000001414 11542603374 014625 0 ustar sluggo sluggo [egg_info]
tag_build =
tag_date = 0
tag_svn_revision = 0
[pudge]
template_dir = docs/pudge_template
title = WebHelpers
dest = docs/html
docs = docs/index.txt
settings = no_about=true
link1=/community/ Community
link2=/download/ Download
extra_credits=Development Sponsored by Parachute LLC.
modules = webhelpers, webhelpers.rails
doc_base = docs/
blog_url = http://groovie.org/articles/category/python
organization = WebHelpers
organization_url = http://pylonshq.com/WebHelpers/
trac_url = http://pylonshq.com/project/
[publish]
doc-dest = scp://bbangert@groovie.org/usr/local/www/pylonshq/WebHelpers/
make-dirs = 1
doc-dir = docs/html
[nosetests]
exclude = webhelpers.feedgenerator
verbosity = 2
verbose = True
with-doctest = True
WebHelpers-1.3/webhelpers/ 0000775 0001750 0001750 00000000000 11542603374 015146 5 ustar sluggo sluggo WebHelpers-1.3/webhelpers/misc.py 0000664 0001750 0001750 00000014667 11376653022 016471 0 ustar sluggo sluggo """Helpers that are neither text, numeric, container, or date.
"""
import itertools
import traceback
import types
import warnings
def all(seq, pred=None):
"""Is ``pred(elm)`` true for all elements?
With the default predicate, this is the same as Python 2.5's ``all()``
function; i.e., it returns true if all elements are true.
>>> all(["A", "B"])
True
>>> all(["A", ""])
False
>>> all(["", ""])
False
>>> all(["A", "B", "C"], lambda x: x <= "C")
True
>>> all(["A", "B", "C"], lambda x: x < "C")
False
From recipe in itertools docs.
"""
for elm in itertools.ifilterfalse(pred, seq):
return False
return True
def any(seq, pred=None):
"""Is ``pred(elm)`` is true for any element?
With the default predicate, this is the same as Python 2.5's ``any()``
function; i.e., it returns true if any element is true.
>>> any(["A", "B"])
True
>>> any(["A", ""])
True
>>> any(["", ""])
False
>>> any(["A", "B", "C"], lambda x: x <= "C")
True
>>> any(["A", "B", "C"], lambda x: x < "C")
True
From recipe in itertools docs.
"""
for elm in itertools.ifilter(pred, seq):
return True
return False
def no(seq, pred=None):
"""Is ``pred(elm)`` false for all elements?
With the default predicate, this returns true if all elements are false.
>>> no(["A", "B"])
False
>>> no(["A", ""])
False
>>> no(["", ""])
True
>>> no(["A", "B", "C"], lambda x: x <= "C")
False
>>> no(["X", "Y", "Z"], lambda x: x <="C")
True
From recipe in itertools docs.
"""
for elm in itertools.ifilter(pred, seq):
return False
return True
def count_true(seq, pred=lambda x: x):
"""How many elements is ``pred(elm)`` true for?
With the default predicate, this counts the number of true elements.
>>> count_true([1, 2, 0, "A", ""])
3
>>> count_true([1, "A", 2], lambda x: isinstance(x, int))
2
This is equivalent to the ``itertools.quantify`` recipe, which I couldn't
get to work.
"""
ret = 0
for x in seq:
if pred(x):
ret += 1
return ret
def convert_or_none(value, type_):
"""Return the value converted to the type, or None if error.
``type_`` may be a Python type or any function taking one argument.
>>> print convert_or_none("5", int)
5
>>> print convert_or_none("A", int)
None
"""
try:
return type_(value)
except Exception:
return None
def flatten(iterable):
"""Recursively iterate lists and tuples.
Examples:
>>> list(flatten([1, [2, 3], 4]))
[1, 2, 3, 4]
>>> list(flatten([1, (2, 3, [4]), 5]))
[1, 2, 3, 4, 5]
"""
for elm in iterable:
if isinstance(elm, (list, tuple)):
for relm in flatten(elm):
yield relm
else:
yield elm
def subclasses_only(class_, it, exclude=None):
"""Extract the subclasses of a class from a module, dict, or iterable.
Return a list of subclasses found. The class itself will not be included.
This is useful to collect the concrete subclasses of an abstract base
class.
``class_`` is a class.
``it`` is a dict or iterable. If a dict is passed, examine its values,
not its keys. To introspect the current module, pass ``globals()``. To
introspect another module or namespace, pass
``vars(the_module_or_namespace)``.
``exclude`` is an optional list of additional classes to ignore.
This is mainly used to exclude abstract subclasses.
"""
if isinstance(it, dict):
it = it.itervalues()
class_types = (type, types.ClassType)
ignore = [class_]
if exclude:
ignore.extend(exclude)
return [x for x in it if isinstance(x, class_types) and
issubclass(x, class_) and x not in ignore]
class NotGiven(object):
"""A default value for function args.
Use this when you need to distinguish between ``None`` and no value.
Example::
>>> def foo(arg=NotGiven):
... print arg is NotGiven
...
>>> foo()
True
>>> foo(None)
False
"""
pass
class DeclarativeException(Exception):
"""A simpler way to define an exception with a fixed message.
Subclasses have a class attribute ``.message``, which is used if no
message is passed to the constructor. The default message is the empty
string.
Example::
>>> class MyException(DeclarativeException):
... message="can't frob the bar when foo is enabled"
...
>>> try:
... raise MyException()
... except Exception, e:
... print e
...
can't frob the bar when foo is enabled
"""
message = ""
def __init__(self, message=None):
Exception.__init__(self, message or self.message)
class OverwriteError(Exception):
"""Refusing to overwrite an existing file or directory."""
def __init__(self, filename, message="not overwriting '%s'"):
message %= (filename,)
Exception.__init__(self, message)
self.filename = filename
def format_exception(exc=None):
"""Format the exception type and value for display, without the traceback.
This is the function you always wished were in the ``traceback`` module but
isn't. It's *different* from ``traceback.format_exception``, which includes
the traceback, returns a list of lines, and has a trailing newline.
If you don't provide an exception object as an argument, it will call
``sys.exc_info()`` to get the current exception.
"""
if exc:
exc_type = type(exc)
else:
exc_type, exc = sys.exc_info()[:2]
lines = traceback.format_exception_only(exc_type, exc)
return "".join(lines).rstrip()
def deprecate(message, pending=False, stacklevel=2):
"""Issue a deprecation warning.
``message``: the deprecation message.
``pending``: if true, use ``PendingDeprecationWarning``. If false (default),
use ``DeprecationWarning``. Python displays deprecations and ignores
pending deprecations by default.
``stacklevel``: passed to ``warnings.warn``. The default level 2 makes the
traceback end at the caller's level. Higher numbers make it end at higher
levels.
"""
category = pending and PendingDeprecationWarning or DeprecationWarning
warnings.warn(message, category, stacklevel)
if __name__ == "__main__":
import doctest
doctest.testmod()
WebHelpers-1.3/webhelpers/constants.py 0000664 0001750 0001750 00000025117 11373734100 017534 0 ustar sluggo sluggo # -*- encoding: latin-1 -*-
# Latin-1 encoding needed for countries list.
"""Place names and other constants often used in web forms.
"""
def uk_counties():
"""\
Return a list of UK county names.
"""
# Based on http://www.gbet.com/AtoZ_counties/
# Updated 2007-10-24
return [x.strip()[2:] for x in """\
* Avon
* Bedfordshire
* Berkshire
* Borders
* Buckinghamshire
* Cambridgeshire
* Central
* Cheshire
* Cleveland
* Clwyd
* Cornwall
* County Antrim
* County Armagh
* County Down
* County Fermanagh
* County Londonderry
* County Tyrone
* Cumbria
* Derbyshire
* Devon
* Dorset
* Dumfries and Galloway
* Durham
* Dyfed
* East Sussex
* Essex
* Fife
* Gloucestershire
* Grampian
* Greater Manchester
* Gwent
* Gwynedd County
* Hampshire
* Herefordshire
* Hertfordshire
* Highlands and Islands
* Humberside
* Isle of Wight
* Kent
* Lancashire
* Leicestershire
* Lincolnshire
* Lothian
* Merseyside
* Mid Glamorgan
* Norfolk
* North Yorkshire
* Northamptonshire
* Northumberland
* Nottinghamshire
* Oxfordshire
* Powys
* Rutland
* Shropshire
* Somerset
* South Glamorgan
* South Yorkshire
* Staffordshire
* Strathclyde
* Suffolk
* Surrey
* Tayside
* Tyne and Wear
* Warwickshire
* West Glamorgan
* West Midlands
* West Sussex
* West Yorkshire
* Wiltshire
* Worcestershire""".split('\n')]
_country_codes = None
def country_codes():
"""Return a list of all country names as tuples. The tuple value is the
country's 2-letter ISO code and its name; e.g.,
``("GB", "United Kingdom")``. The countries are in name order.
Can be used like this::
import webhelpers.constants as constants
from webhelpers.html.tags import select
select("country", country_codes(),
prompt="Please choose a country ...")
See here for more information:
http://www.iso.org/iso/english_country_names_and_code_elements
"""
# Updated on 2007-10-24.
#
# This might seem a funny implementation but it makes it easier to update
# next time there is a change
global _country_codes
if _country_codes is not None:
return _country_codes
else:
text_directly_from_iso_website = u"""
A
AFGHANISTAN AF
ÅLAND ISLANDS AX
ALBANIA AL
ALGERIA DZ
AMERICAN SAMOA AS
ANDORRA AD
ANGOLA AO
ANGUILLA AI
ANTARCTICA AQ
ANTIGUA AND BARBUDA AG
ARGENTINA AR
ARMENIA AM
ARUBA AW
AUSTRALIA AU
AUSTRIA AT
AZERBAIJAN AZ
B
BAHAMAS BS
BAHRAIN BH
BANGLADESH BD
BARBADOS BB
BELARUS BY
BELGIUM BE
BELIZE BZ
BENIN BJ
BERMUDA BM
BHUTAN BT
BOLIVIA BO
BOSNIA AND HERZEGOVINA BA
BOTSWANA BW
BOUVET ISLAND BV
BRAZIL BR
BRITISH INDIAN OCEAN TERRITORY IO
BRUNEI DARUSSALAM BN
BULGARIA BG
BURKINA FASO BF
BURUNDI BI
C
CAMBODIA KH
CAMEROON CM
CANADA CA
CAPE VERDE CV
CAYMAN ISLANDS KY
CENTRAL AFRICAN REPUBLIC CF
CHAD TD
CHILE CL
CHINA CN
CHRISTMAS ISLAND CX
COCOS (KEELING) ISLANDS CC
COLOMBIA CO
COMOROS KM
CONGO CG
CONGO, THE DEMOCRATIC REPUBLIC OF THE CD
COOK ISLANDS CK
COSTA RICA CR
CÔTE D'IVOIRE CI
CROATIA HR
CUBA CU
CYPRUS CY
CZECH REPUBLIC CZ
D
DENMARK DK
DJIBOUTI DJ
DOMINICA DM
DOMINICAN REPUBLIC DO
E
ECUADOR EC
EGYPT EG
EL SALVADOR SV
EQUATORIAL GUINEA GQ
ERITREA ER
ESTONIA EE
ETHIOPIA ET
F
FALKLAND ISLANDS (MALVINAS) FK
FAROE ISLANDS FO
FIJI FJ
FINLAND FI
FRANCE FR
FRENCH GUIANA GF
FRENCH POLYNESIA PF
FRENCH SOUTHERN TERRITORIES TF
G
GABON GA
GAMBIA GM
GEORGIA GE
GERMANY DE
GHANA GH
GIBRALTAR GI
GREECE GR
GREENLAND GL
GRENADA GD
GUADELOUPE GP
GUAM GU
GUATEMALA GT
GUERNSEY GG
GUINEA GN
GUINEA-BISSAU GW
GUYANA GY
H
HAITI HT
HEARD ISLAND AND MCDONALD ISLANDS HM
HOLY SEE (VATICAN CITY STATE) VA
HONDURAS HN
HONG KONG HK
HUNGARY HU
I
ICELAND IS
INDIA IN
INDONESIA ID
IRAN, ISLAMIC REPUBLIC OF IR
IRAQ IQ
IRELAND IE
ISLE OF MAN IM
ISRAEL IL
ITALY IT
J
JAMAICA JM
JAPAN JP
JERSEY JE
JORDAN JO
K
KAZAKHSTAN KZ
KENYA KE
KIRIBATI KI
KOREA, DEMOCRATIC PEOPLE'S REPUBLIC OF KP
KOREA, REPUBLIC OF KR
KUWAIT KW
KYRGYZSTAN KG
L
LAO PEOPLE'S DEMOCRATIC REPUBLIC LA
LATVIA LV
LEBANON LB
LESOTHO LS
LIBERIA LR
LIBYAN ARAB JAMAHIRIYA LY
LIECHTENSTEIN LI
LITHUANIA LT
LUXEMBOURG LU
M
MACAO MO
MACEDONIA, THE FORMER YUGOSLAV REPUBLIC OF MK
MADAGASCAR MG
MALAWI MW
MALAYSIA MY
MALDIVES MV
MALI ML
MALTA MT
MARSHALL ISLANDS MH
MARTINIQUE MQ
MAURITANIA MR
MAURITIUS MU
MAYOTTE YT
MEXICO MX
MICRONESIA, FEDERATED STATES OF FM
MOLDOVA, REPUBLIC OF MD
MONACO MC
MONGOLIA MN
MONTENEGRO ME
MONTSERRAT MS
MOROCCO MA
MOZAMBIQUE MZ
MYANMAR MM
N
NAMIBIA NA
NAURU NR
NEPAL NP
NETHERLANDS NL
NETHERLANDS ANTILLES AN
NEW CALEDONIA NC
NEW ZEALAND NZ
NICARAGUA NI
NIGER NE
NIGERIA NG
NIUE NU
NORFOLK ISLAND NF
NORTHERN MARIANA ISLANDS MP
NORWAY NO
O
OMAN OM
P
PAKISTAN PK
PALAU PW
PALESTINIAN TERRITORY, OCCUPIED PS
PANAMA PA
PAPUA NEW GUINEA PG
PARAGUAY PY
PERU PE
PHILIPPINES PH
PITCAIRN PN
POLAND PL
PORTUGAL PT
PUERTO RICO PR
Q
QATAR QA
R
RÉUNION RE
ROMANIA RO
RUSSIAN FEDERATION RU
RWANDA RW
S
SAINT BARTHÉLEMY BL
SAINT HELENA SH
SAINT KITTS AND NEVIS KN
SAINT LUCIA LC
SAINT MARTIN MF
SAINT PIERRE AND MIQUELON PM
SAINT VINCENT AND THE GRENADINES VC
SAMOA WS
SAN MARINO SM
SAO TOME AND PRINCIPE ST
SAUDI ARABIA SA
SENEGAL SN
SERBIA RS
SEYCHELLES SC
SIERRA LEONE SL
SINGAPORE SG
SLOVAKIA SK
SLOVENIA SI
SOLOMON ISLANDS SB
SOMALIA SO
SOUTH AFRICA ZA
SOUTH GEORGIA AND THE SOUTH SANDWICH ISLANDS GS
SPAIN ES
SRI LANKA LK
SUDAN SD
SURINAME SR
SVALBARD AND JAN MAYEN SJ
SWAZILAND SZ
SWEDEN SE
SWITZERLAND CH
SYRIAN ARAB REPUBLIC SY
T
TAIWAN, PROVINCE OF CHINA TW
TAJIKISTAN TJ
TANZANIA, UNITED REPUBLIC OF TZ
THAILAND TH
TIMOR-LESTE TL
TOGO TG
TOKELAU TK
TONGA TO
TRINIDAD AND TOBAGO TT
TUNISIA TN
TURKEY TR
TURKMENISTAN TM
TURKS AND CAICOS ISLANDS TC
TUVALU TV
U
UGANDA UG
UKRAINE UA
UNITED ARAB EMIRATES AE
UNITED KINGDOM GB
UNITED STATES US
UNITED STATES MINOR OUTLYING ISLANDS UM
URUGUAY UY
UZBEKISTAN UZ
V
VANUATU VU
VATICAN CITY STATE see HOLY SEE
VENEZUELA VE
VIET NAM VN
VIRGIN ISLANDS, BRITISH VG
VIRGIN ISLANDS, U.S. VI
W
WALLIS AND FUTUNA WF
WESTERN SAHARA EH
Y
YEMEN YE
Z
ZAIRE see CONGO, THE DEMOCRATIC REPUBLIC OF THE
ZAMBIA ZM
ZIMBABWE ZW
""".replace('\t',' ').split('\n')
e = []
for item in text_directly_from_iso_website:
if len(item) > 1:
p=[]
parts = item.split(' ')
for part in parts:
if part.strip():
p.append(part.strip())
if len(p)>2:
raise Exception("Invalid entry %s" % p)
p.reverse()
if len(p) == 1:
# It is just a letter
continue
if len(p[0]) != 2:
if p[0][:3] != 'see':
raise Exception('Unknown entry %s'%(p))
else:
# We just want to ignore it
continue
p = tuple(p)
e.append(p)
_country_codes = e
return _country_codes
def us_states():
"""List of USA states.
Return a list of ``(abbreviation, name)`` for all US states, sorted by name.
Includes the District of Columbia.
"""
# From http://www.usps.com/ncsc/lookups/abbreviations.html
#Updated 2008-05-01
return [
("AL", "Alabama"),
("AK", "Alaska"),
("AZ", "Arizona"),
("AR", "Arkansas"),
("CA", "California"),
("CO", "Colorado"),
("CT", "Connecticut"),
("DE", "Delaware"),
("DC", "District of Columbia"),
("FL", "Florida"),
("GA", "Georgia"),
("HI", "Hawaii"),
("ID", "Idaho"),
("IL", "Illinois"),
("IN", "Indiana"),
("IA", "Iowa"),
("KS", "Kansas"),
("KY", "Kentucky"),
("LA", "Louisiana"),
("ME", "Maine"),
("MD", "Maryland"),
("MA", "Massachusetts"),
("MI", "Michigan"),
("MN", "Minnesota"),
("MS", "Mississippi"),
("MO", "Missouri"),
("MT", "Montana"),
("NE", "Nebraska"),
("NV", "Nevada"),
("NH", "New Hampshire"),
("NJ", "New Jersey"),
("NM", "New Mexico"),
("NY", "New York"),
("NC", "North Carolina"),
("ND", "North Dakota"),
("OH", "Ohio"),
("OK", "Oklahoma"),
("OR", "Oregon"),
("PA", "Pennsylvania"),
("RI", "Rhode Island"),
("SC", "South Carolina"),
("SD", "South Dakota"),
("TN", "Tennessee"),
("TX", "Texas"),
("UT", "Utah"),
("VT", "Vermont"),
("VA", "Virginia"),
("WA", "Washington"),
("WV", "West Virginia"),
("WI", "Wisconsin"),
("WY", "Wyoming"),
]
def us_territories():
"""USA postal abbreviations for territories, protectorates, and military.
The return value is a list of ``(abbreviation, name)`` tuples. The
locations are sorted by name.
"""
# From http://www.usps.com/ncsc/lookups/abbreviations.html
# Updated 2008-05-01
return [
("AS", "American Samoa"),
("AA", "Armed Forces Americas"),
("AE", "Armed Forces Europe/Canada/Middle East/Africa"),
("AP", "Armed Forces Pacific"),
("FM", "Federated States of Micronesia"),
("GU", "Guam"),
("MH", "Marshall Islands"),
("MP", "Northern Mariana Islands"),
("PW", "Palau"),
("PR", "Puerto Rico"),
("VI", "Virgin Islands"),
]
def canada_provinces():
"""List of Canadian provinces.
Return a list of ``(abbreviation, name)`` tuples for all Canadian
provinces and territories, sorted by name.
"""
# Based on:
# http://en.wikipedia.org/wiki/Canadian_subnational_postal_abbreviations
# See also:
# http://en.wikipedia.org/wiki/Provinces_and_territories_of_Canada
# Updated 2008-05-01
provinces = [
("Alberta", "AB"),
("British Columbia", "BC"),
("Manitoba", "MB"),
("New Brunswick", "NB"),
("Newfoundland and Labrador", "NL"),
("Nova Scotia", "NS"),
("Northwest Territories", "NT"),
("Nunavut", "NU"),
("Ontario", "ON"),
("Prince Edward Island", "PE"),
("Quebec", "QC"),
("Saskatchewan", "SK"),
("Yukon", "YT"),
]
provinces.sort()
return [(x[1], x[0]) for x in provinces]
WebHelpers-1.3/webhelpers/pylonslib/ 0000775 0001750 0001750 00000000000 11542603374 017161 5 ustar sluggo sluggo WebHelpers-1.3/webhelpers/pylonslib/_jsmin.py 0000664 0001750 0001750 00000002272 11445174513 021015 0 ustar sluggo sluggo #!/usr/bin/python
raise ImportError("""\
_jsmin has been removed from WebHelpers due to licensing issues
Details are in this module's comments.
A standalone "jsmin" package is available in PyPI.""")
# This module used to contain an algorithm for compressing Javascript code
# to minimize network bandwidth. It was written in C by Douglas Crockford
# (www.crockford.com) in 1992. Baruch Even later ported it to Python, and
# that version was added to WebHelpers. However, it retained Crockford's
# license, which was MIT-style but contained the clause, "The Software shall be
# used for Good, not Evil." Fedora's lawyers have declared this clause
# incompatible with its free-software distribution guidelines. Debian and
# other distributions have similar guidelines. Thus, it can't be included in
# popular Linux distributions we want WebHelpers to be in. The legal argument
# is that while the clause is unenforceably vague ("What is an Evil purpose?"),
# it's an implied restriction on use, which could expose users to trivial
# harassment. Both the WebHelpers maintainer and Fedora maintainers contacted
# Mr Crockford and asked him to change the license. He refused, and so we have
# removed his code.
WebHelpers-1.3/webhelpers/pylonslib/grid.py 0000664 0001750 0001750 00000005022 11540262773 020461 0 ustar sluggo sluggo """
This module is DEPRECATED. Please use ``webhelpers.html.grid`` in new
applications. Support for paged grids has been added to that module in a
framework-neutral way.
PYRAMID USERS: This implementation is incompatible with Pyramid. Use
``webhelpers.html.grid`` instead.
"""
from webhelpers.html.builder import HTML, literal
import webhelpers.html.grid as grid
from webhelpers.misc import deprecate
class NoRequestError(Exception):
pass
class PylonsGrid(grid.Grid):
"""
Subclass of Grid that can handle header link generation for quick building
of tables that support ordering of their contents, paginated results etc.
"""
def __init__(self, request, *args, **kw):
deprecate("please migrate to webhelpers.html.grid")
self.request = request
super(PylonsGrid, self).__init__(*args, **kw)
def generate_header_link(self, column_number, column, label_text):
""" This handles generation of link and then decides to call
self.default_header_ordered_column_format
or
self.default_header_column_format
based on if current column is the one that is used for sorting or not
"""
from pylons import url
# this will handle possible URL generation
request_copy = dict(self.request.copy().GET)
self.order_column = request_copy.pop("order_col", None)
self.order_dir = request_copy.pop("order_dir", None)
if column == self.order_column and self.order_dir == "asc":
new_order_dir = "dsc"
else:
new_order_dir = "asc"
url_href = url.current(order_col=column, order_dir=new_order_dir,
**request_copy)
label_text = HTML.tag("a", href=url_href, c=label_text)
# Is the current column the one we're ordering on?
if column == self.order_column:
return self.default_header_ordered_column_format(column_number,
column,
label_text)
else:
return self.default_header_column_format(column_number, column,
label_text)
class PylonsObjectGrid(PylonsGrid):
""" This grid will work well with sqlalchemy row instances """
def default_column_format(self, column_number, i, record, column_name):
class_name = "c%s" % (column_number)
return HTML.tag("td", getattr(record, column_name), class_=class_name)
WebHelpers-1.3/webhelpers/pylonslib/flash.py 0000664 0001750 0001750 00000030407 11540262773 020636 0 ustar sluggo sluggo """Accumulate messages to show on the next page request.
The ``Flash`` class is useful when you want to redirect to another page and also
show a status message on that page, such as "Changes saved" or
"No previous search found; returning to home page".
THE IMPLEMENTATION DEPENDS ON PYLONS. However, it can easily be adapted
for another web framework.
PYRAMID USERS: use the flash methods built into Pyramid's ``Session`` object.
This implementation is incompatible with Pyramid.
A typical Pylons application instantiates a ``Flash`` object in
myapp/lib/helpers.py::
from webhelpers.pylonslib.flash import Flash as _Flash
flash = _Flash()
The helpers module is then imported into your controllers and
templates as ``h``. Whenever you want to set a message, call the instance::
h.flash("Record deleted.")
You can set additional messages too::
h.flash("Hope you didn't need it.")
Now make a place in your site template for the messages. In Mako you
might do:
.. code-block:: mako
<% messages = h.flash.pop_messages() %>
% if messages:
% for message in messages:
${message}
% endfor
% endif
You can style this to look however you want:
.. code-block:: css
ul#flash-messages {
color: red;
background-color: #FFFFCC;
font-size: larger;
font-style: italic;
margin-left: 40px;
padding: 4px;
list-style: none;
}
Multiple flash objects
======================
You can define multiple flash objects in your application to display
different kinds of messages at different places on the page. For instance,
you might use the main flash object for general messages, and a second
flash object for "Added dookickey" / "Removed doohickey" messages next to a
doohickey manager.
Message categories
==================
WebHelpers 1.0 adds message categories, contributed by Wichert Akkerman.
These work like severity levels in Python's logging system. The standard
categories are "*warning*", "*notice*", "*error*", and "*success*", with
the default being "*notice*". The category is available in the message's
``.category`` attribute, and is normally used to set the container's CSS
class.
This is the *only* thing it does. Calling ``.pop_messages()`` pops all messages
in the order registered, regardless of category. It is *not* possible to pop
only a certain category, or all levels above a certain level, or to group
messages by category. If you want to group different kinds of messages
together, or pop only certain categories while leaving other categories, you
should use multiple ``Flash`` objects.
You can change the standard categories by overriding the ``.categories``
and ``.default_category`` class attributes, or by providing alternate
values using constructor keywords.
Category example
----------------
Let's show a standard way of using flash messages in your site: we will
demonstrate *self-healing messages* (similar to what Growl does on OSX)
to show messages in a site.
To send a message from python just call the flash helper method::
h.flash(u"Settings have been saved")
This will tell the system to show a message in the rendered page. If you need
more control you can specify a message category as well: one of *warning*,
*notice*, *error* or *success*. The default category is *notice*. For example::
h.flash(u"Failed to send confirmation email", "warning")
We will use a very simple markup style: messages will be placed in a ``div``
with id ``selfHealingFeedback`` at the end of the document body. The messages
are standard paragraphs with a class indicating the message category. For
example::
...
...
Succesfully updated your settings
Failed to send confirmation email
This can easily created from a template. If you are using Genshi this
should work:
.. code-block: html
This is a notice.
The needed CSS is very simple:
.. code-block: css
#selfHealingFeedback {
position: fixed;
top: 20px;
left: 20px;
z-index: 2000;
}
#selfHealingFeedback p {
margin-bottom: 10px;
width: 250px;
opacity: 0.93;
}
p.notice,p.error,p.success,p.warning {
border: 3px solid silver;
padding: 10px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
-webkit-box-shadow: 0 0 5px silver;
}
Choosing different colours for the categories is left as an exercise
for the reader.
Next we create the javascript that will manage the needed behaviour (this
implementation is based on jQuery)::
function _SetupMessage(el) {
var remover = function () {
msg.animate({opacity: 0}, "slow")
.slideUp("slow", function() { msg.remove() }); };
msg.data("healtimer", setTimeout(remover, 10000))
.click(function() { clearTimeout(msg.data("healtimer")); remover(); });
}
function ShowMessage(message, category) {
if (!category)
category="notice";
var container = $("#selfHealingFeedback");
if (!container.length)
container=$("").appendTo("body");
var msg = $("").addClass(category).html(message);
SetupMessage(msg);
msg.appendTo(container);
}
$(document).ready(function() {
$("#selfHealingFeedback p").each(function() { SetupMessage($(this)); });
}
The ``SetupMessage`` function configures the desired behaviour: a message
disappears after 10 seconds, or if you click on it. Removal is done using
a simple animation to avoid messages jumping around on the screen.
This function is called for all messages as soon as the document has fully
loaded. The ``ShowMessage`` function works exactly like the ``flash`` method
in python: you can call it with a message and optionally a category and it
will pop up a new message.
JSON integration
----------------
It is not unusual to perform a remote task using a JSON call and show a
result message to the user. This can easily be done using a simple wrapper
around the ShowMessage method::
function ShowJSONResponse(info) {
if (!info.message)
return;
ShowMessage(info.message, info.message_category);
}
You can use this direct as the success callback for the jQuery AJAX method::
$.ajax({type: "POST",
url: "http://your.domain/call/json",
dataType: "json",
success: ShowJSONResponse
});
if you need to perform extra work in your callback method you can call
it yourself as well, for example::
$(document).ready(function() {
$("button").click(function() {
var button = $(this);
button.addClass("processing");
$.ajax({type: "POST",
url: this.form["json_url"].value,
dataType: "json",
success: function(data, status) {
button.removeClass("processing");
ShowJSONResponse(data);
},
error: function(request, status, error) {
button.removeClass("processing");
ShowMessage("JSON call failed", "error");
}
});
return false;
});
});
This sets up a simple form which can be submitted normally by non-javascript
enabled browsers. If a user does have javascript an AJAX call will be made
to the server and the result will be shown in a message. While the call is
active the button will be marked with a *processing* class.
The server can return a message by including a ``message`` field in its
response. Optionally a ``message_category`` field can also be included
which will be used to determine the message category. For example::
@jsonify
def handler(self):
..
..
return dict(message=u"Settings successfully updated")
"""
# Do not import Pylons at module level; only within functions. All WebHelpers
# modules should be importable on any Python system for the standard
# regression tests.
from webhelpers.html import escape
__all__ = ["Flash", "Message"]
class Message(object):
"""A message returned by ``Flash.pop_messages()``.
Converting the message to a string returns the message text. Instances
also have the following attributes:
* ``message``: the message text.
* ``category``: the category specified when the message was created.
"""
def __init__(self, category, message):
self.category=category
self.message=message
def __str__(self):
return self.message
__unicode__ = __str__
def __html__(self):
return escape(self.message)
class Flash(object):
"""Accumulate a list of messages to show at the next page request.
"""
# List of allowed categories. If None, allow any category.
categories = ["warning", "notice", "error", "success"]
# Default category if none is specified.
default_category = "notice"
def __init__(self, session_key="flash", categories=None, default_category=None):
"""Instantiate a ``Flash`` object.
``session_key`` is the key to save the messages under in the user's
session.
``categories`` is an optional list which overrides the default list
of categories.
``default_category`` overrides the default category used for messages
when none is specified.
"""
self.session_key = session_key
if categories is not None:
self.categories = categories
if default_category is not None:
self.default_category = default_category
if self.categories and self.default_category not in self.categories:
raise ValueError("unrecognized default category %r" % (self.default_category,))
def __call__(self, message, category=None, ignore_duplicate=False):
"""Add a message to the session.
``message`` is the message text.
``category`` is the message's category. If not specified, the default
category will be used. Raise ``ValueError`` if the category is not
in the list of allowed categories.
If ``ignore_duplicate`` is true, don't add the message if another
message with identical text has already been added. If the new
message has a different category than the original message, change the
original message to the new category.
"""
if not category:
category = self.default_category
elif self.categories and category not in self.categories:
raise ValueError("unrecognized category %r" % (category,))
# Don't store Message objects in the session, to avoid unpickling
# errors in edge cases.
new_message_tuple = (category, message)
from pylons import session
messages = session.setdefault(self.session_key, [])
# ``messages`` is a mutable list, so changes to the local variable are
# reflected in the session.
if ignore_duplicate:
for i, m in enumerate(messages):
if m[1] == message:
if m[0] != category:
messages[i] = new_message_tuple
session.save()
return # Original message found, so exit early.
messages.append(new_message_tuple)
session.save()
def pop_messages(self):
"""Return all accumulated messages and delete them from the session.
The return value is a list of ``Message`` objects.
"""
from pylons import session
messages = session.pop(self.session_key, [])
session.save()
return [Message(*m) for m in messages]
WebHelpers-1.3/webhelpers/pylonslib/secure_form.py 0000664 0001750 0001750 00000006250 11540262773 022051 0 ustar sluggo sluggo """
Secure Form Tag Helpers -- For prevention of Cross-site request forgery (CSRF)
attacks.
Generates form tags that include client-specific authorization tokens to be
verified by the destined web app.
PYRAMID USERS: Use the csrf_token methods built into Pyramid's ``Session``
object. This implementation is incompatible with Pyramid.
Authorization tokens are stored in the client's session. The web app can then
verify the request's submitted authorization token with the value in the
client's session.
This ensures the request came from the originating page. See
http://en.wikipedia.org/wiki/Cross-site_request_forgery for more information.
Pylons provides an ``authenticate_form`` decorator that does this verification
on the behalf of controllers.
These helpers depend on Pylons' ``session`` object. Most of them can be easily
ported to another framework by changing the API calls.
The helpers are implemented in such a way that it should be easy to create your
own helpers if you are using helpers for AJAX calls.
authentication_token() returns the current authentication token, creating one
and storing it in the session if it doesn't already exist.
auth_token_hidden_field() creates a hidden field (wrapped in an invisible div;
I don't know if this is necessary, but the old WebHelpers had it like this)
containing the authentication token.
secure_form() is form() plus auth_token_hidden_field().
"""
# Do not import Pylons at module level; only within functions. All WebHelpers
# modules should be importable on any Python system for the standard
# regression tests.
import random
from webhelpers.html.builder import HTML, literal
from webhelpers.html.tags import form as insecure_form
from webhelpers.html.tags import hidden
token_key = "_authentication_token"
def authentication_token():
"""Return the current authentication token, creating one if one doesn't
already exist.
"""
from pylons import session
if not token_key in session:
try:
token = str(random.getrandbits(128))
except AttributeError: # Python < 2.4
token = str(random.randrange(2**128))
session[token_key] = token
if hasattr(session, 'save'):
session.save()
return session[token_key]
def auth_token_hidden_field():
token = hidden(token_key, authentication_token())
return HTML.div(token, style="display: none;")
def secure_form(url, method="POST", multipart=False, **attrs):
"""Start a form tag that points the action to an url. This
form tag will also include the hidden field containing
the auth token.
The url options should be given either as a string, or as a
``url()`` function. The method for the form defaults to POST.
Options:
``multipart``
If set to True, the enctype is set to "multipart/form-data".
``method``
The method to use when submitting the form, usually either
"GET" or "POST". If "PUT", "DELETE", or another verb is used, a
hidden input with name _method is added to simulate the verb
over POST.
"""
form = insecure_form(url, method, multipart, **attrs)
token = auth_token_hidden_field()
return literal("%s\n%s" % (form, token))
WebHelpers-1.3/webhelpers/pylonslib/minify.py 0000664 0001750 0001750 00000016526 11540262773 021042 0 ustar sluggo sluggo #!/usr/bin/env python
# -*- coding: utf-8 -*-
# vim: sw=4 ts=4 fenc=utf-8
"""Minification helpers.
This module provides enhanced versions of the ``javascript_link`` and
``stylesheet_link`` helpers in ``webhelpers.html.tags``. These versions add
three additional arguments:
* **minified**: If true, reduce the file size by squeezing out
whitespace and other characters insignificant to the Javascript or CSS syntax.
* **combined**: If true, concatenate the specified files into one file to
reduce page load time.
* **beaker_kwargs** (dict): arguments to pass to ``beaker_cache``.
Dependencies: ``Pylons``, ``Beaker``, ``jsmin``, and ``cssutils`` (all
available in PyPI). If "jsmin" is not installed, the helper issues a warning
and passes Javascript through unchanged. (Changed in WebHelpers 1.1: removed
built-in "_jsmin" package due to licensing issues; details in
webhelpers/pylonslib/_jsmin.py .)
PYRAMID USERS: this implementation is incompatible with Pyramid. No
Pyramid-compatible implementation is currently known.
Contributed by Pedro Algarvio and Domen Kozar .
URL: http://docs.fubar.si/minwebhelpers/
"""
import re
import os
import logging
import StringIO
import warnings
from webhelpers.html.tags import javascript_link as __javascript_link
from webhelpers.html.tags import stylesheet_link as __stylesheet_link
try:
from jsmin import JavascriptMinify
except ImportError:
class JavascriptMinify(object):
def minify(self, instream, outstream):
warnings.warn(JSMIN_MISSING_MESSAGE, UserWarning)
data = instream.read()
outstream.write(data)
instream.close()
JSMIN_MISSING_MESSAGE = """\
_jsmin has been removed from WebHelpers due to licensing issues
Your Javascript code has been passed through unchanged.
You can install the "jsmin" package from PyPI, and this helper will use it.
"""
__all__ = ['javascript_link', 'stylesheet_link']
log = logging.getLogger(__name__)
beaker_kwargs = dict(key='sources',
expire='never',
type='memory')
def combine_sources(sources, ext, fs_root):
if len(sources) < 2:
return sources
names = list()
js_buffer = StringIO.StringIO()
base = os.path.commonprefix([os.path.dirname(s) for s in sources])
for source in sources:
# get a list of all filenames without extensions
js_file = os.path.basename(source)
js_file_name = os.path.splitext(js_file)[0]
names.append(js_file_name)
# build a master file with all contents
full_source = os.path.join(fs_root, source.lstrip('/'))
f = open(full_source, 'r')
js_buffer.write(f.read())
js_buffer.write('\n')
f.close()
# glue a new name and generate path to it
fname = '.'.join(names + ['COMBINED', ext])
fpath = os.path.join(fs_root, base.strip('/'), fname)
# write the combined file
f = open(fpath, 'w')
f.write(js_buffer.getvalue())
f.close()
return [os.path.join(base, fname)]
def minify_sources(sources, ext, fs_root=''):
import cssutils
if 'js' in ext:
js_minify = JavascriptMinify()
minified_sources = []
for source in sources:
# generate full path to source
no_ext_source = os.path.splitext(source)[0]
full_source = os.path.join(fs_root, (no_ext_source + ext).lstrip('/'))
# generate minified source path
full_source = os.path.join(fs_root, (source).lstrip('/'))
no_ext_full_source = os.path.splitext(full_source)[0]
minified = no_ext_full_source + ext
f_minified_source = open(minified, 'w')
# minify js source (read stream is auto-closed inside)
if 'js' in ext:
js_minify.minify(open(full_source, 'r'), f_minified_source)
# minify css source
if 'css' in ext:
serializer = get_serializer()
sheet = cssutils.parseFile(full_source)
sheet.setSerializer(serializer)
cssutils.ser.prefs.useMinified()
f_minified_source.write(sheet.cssText)
f_minified_source.close()
minified_sources.append(no_ext_source + ext)
return minified_sources
def base_link(ext, *sources, **options):
from pylons import config
from pylons.decorators.cache import beaker_cache
combined = options.pop('combined', False)
minified = options.pop('minified', False)
beaker_options = options.pop('beaker_kwargs', False)
fs_root = config.get('pylons.paths').get('static_files')
if not (config.get('debug', False) or options.get('builtins', False)):
if beaker_options:
beaker_kwargs.update(beaker_options)
if combined:
sources = beaker_cache(**beaker_kwargs)(combine_sources)(list(sources), ext, fs_root)
if minified:
sources = beaker_cache(**beaker_kwargs)(minify_sources)(list(sources), '.min.' + ext, fs_root)
if 'js' in ext:
return __javascript_link(*sources, **options)
if 'css' in ext:
return __stylesheet_link(*sources, **options)
def javascript_link(*sources, **options):
return base_link('js', *sources, **options)
def stylesheet_link(*sources, **options):
return base_link('css', *sources, **options)
_serializer_class = None
def get_serializer():
# This is in a function to prevent a global import of ``cssutils``,
# which is not a WebHelpers dependency.
# The class is cached in a global variable so that it will be
# compiled only once.
import cssutils
global _serializer_class
if not _serializer_class:
class CSSUtilsMinificationSerializer(cssutils.CSSSerializer):
def __init__(self, prefs=None):
cssutils.CSSSerializer.__init__(self, prefs)
def do_css_CSSStyleDeclaration(self, style, separator=None):
try:
color = style.getPropertyValue('color')
if color and color is not u'':
color = self.change_colors(color)
style.setProperty('color', color)
except:
pass
return re.sub(r'0\.([\d])+', r'.\1',
re.sub(r'(([^\d][0])+(px|em)+)+', r'\2',
cssutils.CSSSerializer.do_css_CSSStyleDeclaration(
self, style, separator)))
def change_colors(self, color):
colours = {
'black': '#000000',
'fuchia': '#ff00ff',
'yellow': '#ffff00',
'#808080': 'gray',
'#008000': 'green',
'#800000': 'maroon',
'#000800': 'navy',
'#808000': 'olive',
'#800080': 'purple',
'#ff0000': 'red',
'#c0c0c0': 'silver',
'#008080': 'teal'
}
if color.lower() in colours:
color = colours[color.lower()]
if color.startswith('#') and len(color) == 7:
if color[1]==color[2] and color[3]==color[4] and color[5]==color[6]:
color = '#%s%s%s' % (color[1], color[3], color[5])
return color
# End of class CSSUtilsMinificationSerializer
_serializer_class = CSSUtilsMinificationSerializer
return _serializer_class()
WebHelpers-1.3/webhelpers/pylonslib/__init__.py 0000664 0001750 0001750 00000000764 11373653355 021307 0 ustar sluggo sluggo """Helpers for the `Pylons `_ web framework
These helpers depend on Pylons' ``request``, ``response``, ``session``
objects or some other aspect of Pylons. Most of them can be easily ported to
another framework by changing the API calls.
"""
# Do not import Pylons at module level; only within functions. All WebHelpers
# modules should be importable on any Python system for the standard
# regression tests.
# Backward compatibility
from webhelpers.pylonslib.flash import *
WebHelpers-1.3/webhelpers/feedgenerator.py 0000664 0001750 0001750 00000062005 11471126331 020327 0 ustar sluggo sluggo # Copyright (c) Django Software Foundation and individual contributors.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# 3. Neither the name of Django nor the names of its contributors may be used
# to endorse or promote products derived from this software without
# specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# Last synched with Django source 2009-12-18 (Django revision 11910):
# http://code.djangoproject.com/browser/django/trunk/django/utils/feedgenerator.py
# http://code.djangoproject.com/browser/django/trunk/django/contrib/gis/feeds.py
# WebHelpers changes from original:
# ---------------------------------
# - Combine ``django.utils.feedgenerator`` and ``django.contrib.gis.feeds``.
# - Change imports:
# * ``SimpleXMLGenerator`` and ``iri_to_uri`` are in ``webhelpers.util``.
# * Add local copy of ``force_unicode``.
# - Delete parts that depend on Django's ORM system: ``Feed`` class,
# ``BaseFeed`` and ``FeedDoesNotExist`` imports.
# - Delete ``to_unicode`` lambdas and ``force_unicode`` import; these seem
# unnecessary.
# - Change docstring imports.
# - Apply ``rfc3339_date`` bugfix (02044132a2ef) to both that function and
# and ``rfc2822_date``. (``.tzinfo`` attribute may not exist in datetime
# objects.)
# - Apply 'published' property patch (1f234b039b58).
# - Note: 'generator' and 'source' properties were lost from a previous
# revision of webhelpers.feedgenerator. The implementation had a bug and
# can't be used as is.
# - Extend latitude-longitude behavior so that data can be input in either
# lat-lon or lon-lat format. Output is always written in lat-lon per the
# GeoRSS spec. Django's feedgenerator expects lon-lat input for GeoDjango
# compatibility. WebHelpers defaults to lat-lon but can be switched to
# lon-lat by setting the ``GeoFeedMixin.is_input_latitude_first`` flag to
# false. (The flag can be set in a subclass or instance anytime before the
# output is written.) The swapping is done in
# ``GeoFeedMixin.georss_coords()`` and ``.add_georss_point()``.
# - "if geom is not None" syntax fix in ``GeoFeedMixin.get_georss_element()``.
# - Add a ``Geometry`` class for passing geometries to the Geo classes.
# - ``GeoFeedMixin`` docstring.
# - Add a dummy version attribute to ``RssFeed`` base class.
# ``RssFeed._version = # "?"`` This avoids AttributeError when instantiating
# ``RssFeed`` directly, although it's obviously invalid RSS.
"""
Syndication feed generation library -- used for generating RSS, etc.
Sample usage:
>>> import webhelpers.feedgenerator as feedgenerator
>>> feed = feedgenerator.Rss201rev2Feed(
... title=u"Poynter E-Media Tidbits",
... link=u"http://www.poynter.org/column.asp?id=31",
... description=u"A group weblog by the sharpest minds in online media/journalism/publishing.",
... language=u"en",
... )
>>> feed.add_item(title="Hello", link=u"http://www.holovaty.com/test/", description="Testing.")
>>> fp = open('test.rss', 'w')
>>> feed.write(fp, 'utf-8')
>>> fp.close()
For definitions of the different versions of RSS, see:
http://diveintomark.org/archives/2004/02/04/incompatible-rss
"""
import re
import datetime
from webhelpers.util import SimplerXMLGenerator, iri_to_uri
#### The following code comes from ``django.utils.feedgenerator`` ####
def rfc2822_date(date):
# We do this ourselves to be timezone aware, email.Utils is not tz aware.
if getattr(date, "tzinfo", False):
time_str = date.strftime('%a, %d %b %Y %H:%M:%S ')
offset = date.tzinfo.utcoffset(date)
timezone = (offset.days * 24 * 60) + (offset.seconds / 60)
hour, minute = divmod(timezone, 60)
return time_str + "%+03d%02d" % (hour, minute)
else:
return date.strftime('%a, %d %b %Y %H:%M:%S -0000')
def rfc3339_date(date):
if getattr(date, "tzinfo", False):
time_str = date.strftime('%Y-%m-%dT%H:%M:%S')
offset = date.tzinfo.utcoffset(date)
timezone = (offset.days * 24 * 60) + (offset.seconds / 60)
hour, minute = divmod(timezone, 60)
return time_str + "%+03d:%02d" % (hour, minute)
else:
return date.strftime('%Y-%m-%dT%H:%M:%SZ')
def get_tag_uri(url, date):
"Creates a TagURI. See http://diveintomark.org/archives/2004/05/28/howto-atom-id"
tag = re.sub('^http://', '', url)
if date is not None:
tag = re.sub('/', ',%s:/' % date.strftime('%Y-%m-%d'), tag, 1)
tag = re.sub('#', '/', tag)
return u'tag:' + tag
class SyndicationFeed(object):
"Base class for all syndication feeds. Subclasses should provide write()"
def __init__(self, title, link, description, language=None, author_email=None,
author_name=None, author_link=None, subtitle=None, categories=None,
feed_url=None, feed_copyright=None, feed_guid=None, ttl=None, **kwargs):
if categories:
categories = [force_unicode(c) for c in categories]
self.feed = {
'title': title,
'link': iri_to_uri(link),
'description': description,
'language': language,
'author_email': author_email,
'author_name': author_name,
'author_link': iri_to_uri(author_link),
'subtitle': subtitle,
'categories': categories or (),
'feed_url': iri_to_uri(feed_url),
'feed_copyright': feed_copyright,
'id': feed_guid or link,
'ttl': ttl,
}
self.feed.update(kwargs)
self.items = []
def add_item(self, title, link, description, author_email=None,
author_name=None, author_link=None, pubdate=None, comments=None,
unique_id=None, enclosure=None, categories=(), item_copyright=None,
ttl=None, **kwargs):
"""
Adds an item to the feed. All args are expected to be Python Unicode
objects except pubdate, which is a datetime.datetime object, and
enclosure, which is an instance of the Enclosure class.
"""
item = {
'title': title,
'link': iri_to_uri(link),
'description': description,
'author_email': author_email,
'author_name': author_name,
'author_link': iri_to_uri(author_link),
'pubdate': pubdate,
'comments': comments,
'unique_id': unique_id,
'enclosure': enclosure,
'categories': categories or (),
'item_copyright': item_copyright,
'ttl': ttl,
}
item.update(kwargs)
self.items.append(item)
def num_items(self):
return len(self.items)
def root_attributes(self):
"""
Return extra attributes to place on the root (i.e. feed/channel) element.
Called from write().
"""
return {}
def add_root_elements(self, handler):
"""
Add elements in the root (i.e. feed/channel) element. Called
from write().
"""
pass
def item_attributes(self, item):
"""
Return extra attributes to place on each item (i.e. item/entry) element.
"""
return {}
def add_item_elements(self, handler, item):
"""
Add elements on each item (i.e. item/entry) element.
"""
pass
def write(self, outfile, encoding):
"""
Outputs the feed in the given encoding to outfile, which is a file-like
object. Subclasses should override this.
"""
raise NotImplementedError
def writeString(self, encoding):
"""
Returns the feed in the given encoding as a string.
"""
from StringIO import StringIO
s = StringIO()
self.write(s, encoding)
return s.getvalue()
def latest_post_date(self):
"""
Returns the latest item's pubdate. If none of them have a pubdate,
this returns the current date/time.
"""
updates = [i['pubdate'] for i in self.items if i['pubdate'] is not None]
if len(updates) > 0:
updates.sort()
return updates[-1]
else:
return datetime.datetime.now()
class Enclosure(object):
"Represents an RSS enclosure"
def __init__(self, url, length, mime_type):
"All args are expected to be Python Unicode objects"
self.length, self.mime_type = length, mime_type
self.url = iri_to_uri(url)
class RssFeed(SyndicationFeed):
mime_type = 'application/rss+xml'
_version = u"?"
def write(self, outfile, encoding):
handler = SimplerXMLGenerator(outfile, encoding)
handler.startDocument()
handler.startElement(u"rss", self.rss_attributes())
handler.startElement(u"channel", self.root_attributes())
self.add_root_elements(handler)
self.write_items(handler)
self.endChannelElement(handler)
handler.endElement(u"rss")
def rss_attributes(self):
return {u"version": self._version}
def write_items(self, handler):
for item in self.items:
handler.startElement(u'item', self.item_attributes(item))
self.add_item_elements(handler, item)
handler.endElement(u"item")
def add_root_elements(self, handler):
handler.addQuickElement(u"title", self.feed['title'])
handler.addQuickElement(u"link", self.feed['link'])
handler.addQuickElement(u"description", self.feed['description'])
if self.feed['language'] is not None:
handler.addQuickElement(u"language", self.feed['language'])
for cat in self.feed['categories']:
handler.addQuickElement(u"category", cat)
if self.feed['feed_copyright'] is not None:
handler.addQuickElement(u"copyright", self.feed['feed_copyright'])
handler.addQuickElement(u"lastBuildDate", rfc2822_date(self.latest_post_date()).decode('utf-8'))
if self.feed['ttl'] is not None:
handler.addQuickElement(u"ttl", self.feed['ttl'])
def endChannelElement(self, handler):
handler.endElement(u"channel")
class RssUserland091Feed(RssFeed):
_version = u"0.91"
def add_item_elements(self, handler, item):
handler.addQuickElement(u"title", item['title'])
handler.addQuickElement(u"link", item['link'])
if item['description'] is not None:
handler.addQuickElement(u"description", item['description'])
class Rss201rev2Feed(RssFeed):
# Spec: http://blogs.law.harvard.edu/tech/rss
_version = u"2.0"
def add_item_elements(self, handler, item):
handler.addQuickElement(u"title", item['title'])
handler.addQuickElement(u"link", item['link'])
if item['description'] is not None:
handler.addQuickElement(u"description", item['description'])
# Author information.
if item["author_name"] and item["author_email"]:
handler.addQuickElement(u"author", "%s (%s)" % \
(item['author_email'], item['author_name']))
elif item["author_email"]:
handler.addQuickElement(u"author", item["author_email"])
elif item["author_name"]:
handler.addQuickElement(u"dc:creator", item["author_name"], {"xmlns:dc": u"http://purl.org/dc/elements/1.1/"})
if item['pubdate'] is not None:
handler.addQuickElement(u"pubDate", rfc2822_date(item['pubdate']).decode('utf-8'))
if item['comments'] is not None:
handler.addQuickElement(u"comments", item['comments'])
if item['unique_id'] is not None:
handler.addQuickElement(u"guid", item['unique_id'])
if item['ttl'] is not None:
handler.addQuickElement(u"ttl", item['ttl'])
# Enclosure.
if item['enclosure'] is not None:
handler.addQuickElement(u"enclosure", '',
{u"url": item['enclosure'].url, u"length": item['enclosure'].length,
u"type": item['enclosure'].mime_type})
# Categories.
for cat in item['categories']:
handler.addQuickElement(u"category", cat)
class Atom1Feed(SyndicationFeed):
# Spec: http://atompub.org/2005/07/11/draft-ietf-atompub-format-10.html
mime_type = 'application/atom+xml'
ns = u"http://www.w3.org/2005/Atom"
def write(self, outfile, encoding):
handler = SimplerXMLGenerator(outfile, encoding)
handler.startDocument()
handler.startElement(u'feed', self.root_attributes())
self.add_root_elements(handler)
self.write_items(handler)
handler.endElement(u"feed")
def root_attributes(self):
if self.feed['language'] is not None:
return {u"xmlns": self.ns, u"xml:lang": self.feed['language']}
else:
return {u"xmlns": self.ns}
def add_root_elements(self, handler):
handler.addQuickElement(u"title", self.feed['title'])
handler.addQuickElement(u"link", "", {u"rel": u"alternate", u"href": self.feed['link']})
if self.feed['feed_url'] is not None:
handler.addQuickElement(u"link", "", {u"rel": u"self", u"href": self.feed['feed_url']})
handler.addQuickElement(u"id", self.feed['id'])
handler.addQuickElement(u"updated", rfc3339_date(self.latest_post_date()).decode('utf-8'))
if self.feed['author_name'] is not None:
handler.startElement(u"author", {})
handler.addQuickElement(u"name", self.feed['author_name'])
if self.feed['author_email'] is not None:
handler.addQuickElement(u"email", self.feed['author_email'])
if self.feed['author_link'] is not None:
handler.addQuickElement(u"uri", self.feed['author_link'])
handler.endElement(u"author")
if self.feed['subtitle'] is not None:
handler.addQuickElement(u"subtitle", self.feed['subtitle'])
for cat in self.feed['categories']:
handler.addQuickElement(u"category", "", {u"term": cat})
if self.feed['feed_copyright'] is not None:
handler.addQuickElement(u"rights", self.feed['feed_copyright'])
def write_items(self, handler):
for item in self.items:
handler.startElement(u"entry", self.item_attributes(item))
self.add_item_elements(handler, item)
handler.endElement(u"entry")
def add_item_elements(self, handler, item):
handler.addQuickElement(u"title", item['title'])
handler.addQuickElement(u"link", u"", {u"href": item['link'], u"rel": u"alternate"})
if item['pubdate'] is not None:
handler.addQuickElement(u"updated", rfc3339_date(item['pubdate']).decode('utf-8'))
handler.addQuickElement(u"published", rfc3339_date(item['pubdate']).decode('utf-8'))
# Author information.
if item['author_name'] is not None:
handler.startElement(u"author", {})
handler.addQuickElement(u"name", item['author_name'])
if item['author_email'] is not None:
handler.addQuickElement(u"email", item['author_email'])
if item['author_link'] is not None:
handler.addQuickElement(u"uri", item['author_link'])
handler.endElement(u"author")
# Unique ID.
if item['unique_id'] is not None:
unique_id = item['unique_id']
else:
unique_id = get_tag_uri(item['link'], item['pubdate'])
handler.addQuickElement(u"id", unique_id)
# Summary.
if item['description'] is not None:
handler.addQuickElement(u"summary", item['description'], {u"type": u"html"})
# Enclosure.
if item['enclosure'] is not None:
handler.addQuickElement(u"link", '',
{u"rel": u"enclosure",
u"href": item['enclosure'].url,
u"length": item['enclosure'].length,
u"type": item['enclosure'].mime_type})
# Categories.
for cat in item['categories']:
handler.addQuickElement(u"category", u"", {u"term": cat})
# Rights.
if item['item_copyright'] is not None:
handler.addQuickElement(u"rights", item['item_copyright'])
# This isolates the decision of what the system default is, so calling code can
# do "feedgenerator.DefaultFeed" instead of "feedgenerator.Rss201rev2Feed".
DefaultFeed = Rss201rev2Feed
#### The following code comes from ``django.contrib.gis.feeds`` ####
class GeoFeedMixin(object):
"""
This mixin provides the necessary routines for SyndicationFeed subclasses
to produce simple GeoRSS or W3C Geo elements.
Subclasses recognize a ``geometry`` keyword argument to ``.add_item()``.
The value may be any of several types:
* a 2-element tuple or list of floats representing latitude/longitude:
``(X, Y)``. This is called a "point".
* a 4-element tuple or list of floats representing a box:
``(X0, Y0, X1, Y1)``.
* a tuple or list of two points: ``( (X0, Y0), (X1, Y1) )``.
* a ``Geometry`` instance. (Or any compatible class.) This provides
limited support for points, lines, and polygons. Read the ``Geometry``
docstring and the source of ``GeoFeedMixin.add_georss_element()``
before using this.
The mixin provides one class attribute:
.. attribute:: is_input_latitude_first
The default value False indicates that input data is in
latitude/longitude order. Change to True if the input data is
longitude/latitude. The output is always written latitude/longitude
to conform to the GeoRSS spec.
The reason for this attribute is that the Django original stores data
in longitude/latutude order and reverses the arguments before writing.
WebHelpers does not do this by default, but if you're using Django data
or other data that has longitude first, you'll have to set this.
"""
# Set to True if the input data is in lat-lon order, or False if lon-lat.
# The output is always written in lat-lon order.
is_input_latitude_first = True
def georss_coords(self, coords):
"""
In GeoRSS coordinate pairs are ordered by lat/lon and separated by
a single white space. Given a tuple of coordinates, this will return
a unicode GeoRSS representation.
"""
if self.is_input_latitude_first:
return u' '.join([u'%f %f' % x for x in coords])
else:
return u' '.join([u'%f %f' % (x[1], x[0]) for x in coords])
def add_georss_point(self, handler, coords, w3c_geo=False):
"""
Adds a GeoRSS point with the given coords using the given handler.
Handles the differences between simple GeoRSS and the more popular
W3C Geo specification.
"""
if w3c_geo:
if self.is_input_latitude_first:
lat, lon = coords[:2]
else:
lon, lat = coords[:2]
handler.addQuickElement(u'geo:lat', u'%f' % lat)
handler.addQuickElement(u'geo:lon', u'%f' % lon)
else:
handler.addQuickElement(u'georss:point', self.georss_coords((coords,)))
def add_georss_element(self, handler, item, w3c_geo=False):
"""
This routine adds a GeoRSS XML element using the given item and handler.
"""
# Getting the Geometry object.
geom = item.get('geometry', None)
if geom is not None:
if isinstance(geom, (list, tuple)):
# Special case if a tuple/list was passed in. The tuple may be
# a point or a box
box_coords = None
if isinstance(geom[0], (list, tuple)):
# Box: ( (X0, Y0), (X1, Y1) )
if len(geom) == 2:
box_coords = geom
else:
raise ValueError('Only should be two sets of coordinates.')
else:
if len(geom) == 2:
# Point: (X, Y)
self.add_georss_point(handler, geom, w3c_geo=w3c_geo)
elif len(geom) == 4:
# Box: (X0, Y0, X1, Y1)
box_coords = (geom[:2], geom[2:])
else:
raise ValueError('Only should be 2 or 4 numeric elements.')
# If a GeoRSS box was given via tuple.
if not box_coords is None:
if w3c_geo: raise ValueError('Cannot use simple GeoRSS box in W3C Geo feeds.')
handler.addQuickElement(u'georss:box', self.georss_coords(box_coords))
else:
# Getting the lower-case geometry type.
gtype = str(geom.geom_type).lower()
if gtype == 'point':
self.add_georss_point(handler, geom.coords, w3c_geo=w3c_geo)
else:
if w3c_geo: raise ValueError('W3C Geo only supports Point geometries.')
# For formatting consistent w/the GeoRSS simple standard:
# http://georss.org/1.0#simple
if gtype in ('linestring', 'linearring'):
handler.addQuickElement(u'georss:line', self.georss_coords(geom.coords))
elif gtype in ('polygon',):
# Only support the exterior ring.
handler.addQuickElement(u'georss:polygon', self.georss_coords(geom[0].coords))
else:
raise ValueError('Geometry type "%s" not supported.' % geom.geom_type)
### SyndicationFeed subclasses ###
class GeoRSSFeed(Rss201rev2Feed, GeoFeedMixin):
def rss_attributes(self):
attrs = super(GeoRSSFeed, self).rss_attributes()
attrs[u'xmlns:georss'] = u'http://www.georss.org/georss'
return attrs
def add_item_elements(self, handler, item):
super(GeoRSSFeed, self).add_item_elements(handler, item)
self.add_georss_element(handler, item)
def add_root_elements(self, handler):
super(GeoRSSFeed, self).add_root_elements(handler)
self.add_georss_element(handler, self.feed)
class GeoAtom1Feed(Atom1Feed, GeoFeedMixin):
def root_attributes(self):
attrs = super(GeoAtom1Feed, self).root_attributes()
attrs[u'xmlns:georss'] = u'http://www.georss.org/georss'
return attrs
def add_item_elements(self, handler, item):
super(GeoAtom1Feed, self).add_item_elements(handler, item)
self.add_georss_element(handler, item)
def add_root_elements(self, handler):
super(GeoAtom1Feed, self).add_root_elements(handler)
self.add_georss_element(handler, self.feed)
class W3CGeoFeed(Rss201rev2Feed, GeoFeedMixin):
def rss_attributes(self):
attrs = super(W3CGeoFeed, self).rss_attributes()
attrs[u'xmlns:geo'] = u'http://www.w3.org/2003/01/geo/wgs84_pos#'
return attrs
def add_item_elements(self, handler, item):
super(W3CGeoFeed, self).add_item_elements(handler, item)
self.add_georss_element(handler, item, w3c_geo=True)
def add_root_elements(self, handler):
super(W3CGeoFeed, self).add_root_elements(handler)
self.add_georss_element(handler, self.feed, w3c_geo=True)
class Geometry(object):
"""A basic geometry class for ``GeoFeedMixin``.
Instances have two public attributes:
.. attribute:: geom_type
"point", "linestring", "linearring", "polygon"
.. attribute:: coords
For **point**, a tuple or list of two floats: ``(X, Y)``.
For **linestring** or **linearring**, a string: ``"X0 Y0 X1 Y1 ..."``.
For **polygon**, a list of strings: ``["X0 Y0 X1 Y1 ..."]``. Only the
first element is used because the Geo classes support only the exterior
ring.
The constructor does not check its argument types.
This class was created for WebHelpers based on the interface expected by
``GeoFeedMixin.add_georss_element()``. The class is untested. Please send
us feedback on whether it works for you.
"""
def __init__(self, geom_type, coords):
self.geom_type = geom_type
coords = coords
WebHelpers-1.3/webhelpers/html/ 0000775 0001750 0001750 00000000000 11542603374 016112 5 ustar sluggo sluggo WebHelpers-1.3/webhelpers/html/grid.py 0000664 0001750 0001750 00000034537 11540534212 017415 0 ustar sluggo sluggo """A helper to make an HTML table from a list of dicts, objects, or sequences.
A set of CSS styles complementing this helper is in
"webhelpers/html/public/stylesheets/grid.css". To use them, include the
stylesheet in your applcation and set your
class to "stylized".
The documentation below is not very clear. This is a known bug. We need a
native English speaker who uses the module to volunteer to rewrite it.
This module is written and maintained by Ergo^.
"""
from webhelpers.html.builder import HTML, literal
class Grid(object):
"""
This class is designed to aid programmer in the task of creation of
tables/grids - structures that are mostly built from datasets.
To create a grid at minimum one one needs to pass a dataset,
like a list of dictionaries, or sqlalchemy proxy or query object::
grid = Grid(itemlist, ['_numbered','c1', 'c2','c4'])
where itemlist in this simple scenario is a list of dicts:
[{'c1':1,'c2'...}, {'c1'...}, ...]
This helper also received the list that defines order in which
columns will be rendered - also keep note of special column name that can be
passed in list that defines order - ``_numbered`` - this adds additional
column that shows the number of item. For paging sql data there one can pass
``start_number`` argument to the grid to define where to start counting.
Descendant sorting on ``_numbered`` column decrements the value, you can
change how numberign function behaves by overloading ``calc_row_no``
property.
Converting the grid to a string renders the table rows. That's *just*
the
tags, not the
around them. The part outside the
s
have too many variations for us to render it. In many template systems,
you can simply assign the grid to a template variable and it will be
automatically converted to a string. Example using a Mako template:
.. code-block:: html
My Lovely Grid
${my_grid}
The names of the columns will get automatically converted for
humans ie. foo_bar becomes Foo Bar. If you want the title to be something
else you can change the grid.labels dict. If you want the column ``part_no``
to become ``Catalogue Number`` just do::
grid.labels[``part_no``] = u'Catalogue Number'
It may be desired to exclude some or all columns from generation sorting
urls (used by subclasses that are sorting aware). You can use grids
exclude_ordering property to pass list of columns that should not support
sorting. By default sorting is disabled - this ``exclude_ordering`` contains
every column name.
Since various programmers have different needs, Grid is highly customizable.
By default grid attempts to read the value from dict directly by key.
For every column it will try to output value of current_row['colname'].
Since very often this behavior needs to be overridden like we need date
formatted, use conditionals or generate a link one can use
the ``column_formats`` dict and pass a rendering function/lambda to it.
For example we want to apppend ``foo`` to part number::
def custem_part_no_td(col_num, i, item):
return HTML.td(`Foo %s` % item[``part_no``])
grid.column_formats[``part_no``] = custem_part_no_td
You can customize the grids look and behavior by overloading grids instance
render functions::
grid.default_column_format(self, column_number, i, record, column_name)
by default generates markup like:
VALUE
grid.default_header_column_format(self, column_number, column_name,
header_label)
by default generates markup like:
VALUE
grid.default_header_ordered_column_format(self, column_number, order,
column_name, header_label)
Used by grids that support ordering of columns in the grid like,
webhelpers.pylonslib.grid.GridPylons.
by default generates markup like:
LABEL
grid.default_header_record_format(self, headers)
by default generates markup like:
HEADERS_MARKUP
grid.default_record_format(self, i, record, columns)
Make an HTML table from a list of objects, and soon a list of
sequences, a list of dicts, and a single dict.
RECORD_MARKUP
grid.generate_header_link(self, column_number, column, label_text)
by default just sets the order direction and column properties for grid.
Actual link generation is handled by sublasses of Grid.
grid.numbered_column_format(self, column_number, i, record)
by default generates markup like:
RECORD_NO
"""
def __init__(self, itemlist, columns, column_labels=None,
column_formats=None, start_number=1,
order_column=None, order_direction=None, request=None,
url=None, **kw):
""" additional keywords are appended to self.additional_kw
handy for url generation """
self.labels = column_labels or {}
self.exclude_ordering = columns
self.itemlist = itemlist
self.columns = columns
self.column_formats = column_formats or {}
if "_numbered" in columns:
self.labels["_numbered"] = "#"
if "_numbered" not in self.column_formats:
self.column_formats["_numbered"] = self.numbered_column_format
self.start_number = start_number
self.order_dir = order_direction
self.order_column = order_column
#backward compatibility with old pylons grid
if not hasattr(self,'request'):
self.request = request
self.url_generator = url
self.additional_kw = kw
def calc_row_no(self, i, column):
if self.order_dir == 'dsc' and self.order_column == column:
return self.start_number - i
else:
return self.start_number + i
def make_headers(self):
header_columns = []
for i, column in enumerate(self.columns):
# let"s generate header column contents
label_text = ""
if column in self.labels:
label_text = self.labels[column]
else:
label_text = column.replace("_", " ").title()
# handle non clickable columns
if column in self.exclude_ordering:
header = self.default_header_column_format(i + 1, column,
label_text)
# handle clickable columns
else:
header = self.generate_header_link(i + 1, column, label_text)
header_columns.append(header)
return HTML(*header_columns)
def make_columns(self, i, record):
columns = []
for col_num, column in enumerate(self.columns):
if column in self.column_formats:
r = self.column_formats[column](col_num + 1,
self. calc_row_no(i, column),
record)
else:
r = self.default_column_format(col_num + 1,
self.calc_row_no(i, column),
record, column)
columns.append(r)
return HTML(*columns)
def __html__(self):
""" renders the grid """
records = []
#first render headers record
headers = self.make_headers()
r = self.default_header_record_format(headers)
records.append(r)
# now lets render the actual item grid
for i, record in enumerate(self.itemlist):
columns = self.make_columns(i, record)
if hasattr(self, 'custom_record_format'):
r = self.custom_record_format(i + 1, record, columns)
else:
r = self.default_record_format(i + 1, record, columns)
records.append(r)
return HTML(*records)
def __str__(self):
return self.__html__()
def generate_header_link(self, column_number, column, label_text):
""" This handles generation of link and then decides to call
``self.default_header_ordered_column_format``
or
``self.default_header_column_format``
based on whether current column is the one that is used for sorting.
you need to extend Grid class and overload this method implementing
ordering here, whole operation consists of setting
self.order_column and self.order_dir to their CURRENT values,
and generating new urls for state that header should set set after its
clicked
(additional kw are passed to url gen. - like for webhelpers.paginate)
example URL generation code below::
GET = dict(self.request.copy().GET) # needs dict() for py2.5 compat
self.order_column = GET.pop("order_col", None)
self.order_dir = GET.pop("order_dir", None)
# determine new order
if column == self.order_column and self.order_dir == "asc":
new_order_dir = "dsc"
else:
new_order_dir = "asc"
self.additional_kw['order_col'] = column
self.additional_kw['order_dir'] = new_order_dir
# generate new url for example url_generator uses
# pylons's url.current() or pyramid's current_route_url()
new_url = self.url_generator(**self.additional_kw)
# set label for header with link
label_text = HTML.tag("a", href=new_url, c=label_text)
"""
# Is the current column the one we're ordering on?
if (column == self.order_column):
return self.default_header_ordered_column_format(column_number,
column,
label_text)
else:
return self.default_header_column_format(column_number, column,
label_text)
#### Default HTML tag formats ####
def default_column_format(self, column_number, i, record, column_name):
class_name = "c%s" % (column_number)
return HTML.tag("td", record[column_name], class_=class_name)
def numbered_column_format(self, column_number, i, record):
class_name = "c%s" % (column_number)
return HTML.tag("td", i, class_=class_name)
def default_record_format(self, i, record, columns):
if i % 2 == 0:
class_name = "even r%s" % i
else:
class_name = "odd r%s" % i
return HTML.tag("tr", columns, class_=class_name)
def default_header_record_format(self, headers):
return HTML.tag("tr", headers, class_="header")
def default_header_ordered_column_format(self, column_number, column_name,
header_label):
header_label = HTML(header_label, HTML.tag("span", class_="marker"))
if column_name == "_numbered":
column_name = "numbered"
class_name = "c%s ordering %s %s" % (column_number, self.order_dir, column_name)
return HTML.tag("td", header_label, class_=class_name)
def default_header_column_format(self, column_number, column_name,
header_label):
if column_name == "_numbered":
column_name = "numbered"
if column_name in self.exclude_ordering:
class_name = "c%s %s" % (column_number, column_name)
return HTML.tag("td", header_label, class_=class_name)
else:
header_label = HTML(header_label, HTML.tag("span", class_="marker"))
class_name = "c%s ordering %s" % (column_number, column_name)
return HTML.tag("td", header_label, class_=class_name)
class ObjectGrid(Grid):
""" A grid class for a sequence of objects.
This grid class assumes that the rows are objects rather than dicts, and
uses attribute access to retrieve the column values. It works well with
SQLAlchemy ORM instances.
"""
def default_column_format(self, column_number, i, record, column_name):
class_name = "c%s" % (column_number)
return HTML.tag("td", getattr(record, column_name), class_=class_name)
class ListGrid(Grid):
""" A grid class for a sequence of lists.
This grid class assumes that the rows are lists rather than dicts, and
uses subscript access to retrieve the column values. Some constructor args
are also different.
If ``columns`` is not specified in the constructor, it will examine
``itemlist[0]`` to determine the number of columns, and display them in
order. This works only if ``itemlist`` is a sequence and not just an
iterable. Alternatively, you can pass an int to specify the number of
columns, or a list of int subscripts to override the column order.
Examples::
grid = ListGrid(list_data)
grid = ListGrid(list_data, columns=4)
grid = ListGrid(list_data, columns=[1, 3, 2, 0])
``column_labels`` may be a list of strings. The class will calculate the
appropriate subscripts for the superclass dict.
"""
def __init__(self, itemlist, columns=None, column_labels=None, *args, **kw):
if columns is None:
columns = range(len(itemlist[0]))
elif isinstance(columns, int):
columns = range(columns)
# The superclass requires the ``columns`` elements to be strings.
super_columns = [str(x) for x in columns]
# The superclass requires ``column_labels`` to be a dict.
super_labels = column_labels
if isinstance(column_labels, (list, tuple)):
super_labels = dict(zip(super_columns, column_labels))
Grid.__init__(self, itemlist, super_columns, super_labels, *args, **kw)
def default_column_format(self, column_number, i, record, column_name):
class_name = "c%s" % (column_number)
return HTML.tag("td", record[int(column_name)], class_=class_name)
WebHelpers-1.3/webhelpers/html/grid_demo.py 0000664 0001750 0001750 00000017652 11376647675 020451 0 ustar sluggo sluggo """Demos for webhelpers.html.grid
Run this module as a script::
python -m webhelpers.html.grid_demo OUTPUT_DIRECTORY
Dec 16 19:39:54 PST 2009
"""
import optparse
import os
import urllib
from webhelpers.html import *
from webhelpers.html.grid import *
from webhelpers.html.tags import link_to
from webhelpers.misc import subclasses_only
# XXX You may find other helpers in webhelpers.html.tags useful too
#### Global constants ####
USAGE = "python -m %s OUTPUT_DIRECTORY" % __name__
DESCRIPTION = """\
Run the demos in this module and put the HTML output in
OUTPUT_DIRECTORY."""
STYLESHEET = """\
/******************* tables ****************/
table.stylized {
background-color: #ffffff;
border-collapse: separate;
border-spacing: 1px;
border-bottom: 2px solid #666666;
margin: 1px 5px 5px 5px;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
width: 100%;
border-collapse: collapse;
}
table.stylized caption {
color: #ffffff;
background-color: #444466;
padding: 5px;
font-size: 1.3em;
font-weight: bold;
margin: 5px 0px 0px 0px;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
}
table.stylized caption a:link,table.stylized caption a:visited {
color: #ffffff;
text-decoration: none;
font-weight: bold;
}
table.stylized caption a:link,table.stylized caption a:hover {
color: #ffcc00;
text-decoration: none;
font-weight: bold;
}
table.stylized thead {
background-color: #ffffff;
}
table.stylized tbody {
background-color: #ffffff;
}
table.stylized tfooter {
background-color: #ffffff;
}
table.stylized th {
text-align: center;
}
table.stylized tr.header {
text-align: center;
}
table.stylized tr.header td, table.stylized th {
text-align: center;
color: #ffffff;
background-color: #444466;
}
table.stylized td {
padding: 5px 5px 5px 5px;
border: 1px solid #dcdcdc;
}
table.stylized tr.odd td {
border-top: 1px solid #999 !important;
background-color: #ffffff;
}
table.stylized tr.even td {
border-top: 1px solid #999 !important;
background-color: #f6f6f6;
}
table.stylized .no {
width: 30px;
}
table.stylized td.ordering{
background-color: #666666 !important;
padding-right: 20px;
}
table.stylized td.ordering.dsc .marker {
height: 20px;
width: 20px;
display: block;
float: right;
margin: 0px -18px;
/* background-image for neutral marker here */
}
table.stylized td.ordering.dsc .marker {
/* background-image for dsc marker here */
}
table.stylized td.ordering.asc .marker {
/* background-image for asc marker here */
}
table.stylized .header a:link,table.stylized .header a:visited {
color: #ffffff;
text-decoration: none;
font-weight: bold;
}
table.stylized td.ordering a:link,table.stylized td.ordering a:visited {
color: #ffcc00;
text-decoration: none;
font-weight: bold;
}
"""
HTML_TEMPLATE = literal("""\
%(title)s
%(title)s
%(grid)s
%(description)s
""")
# XXX There should be helpers to create a basic HTML file.
test_data = [
{"group_name": "foo", "options": "lalala", "id":1},
{"group_name": "foo2", "options": "lalala2", "id":2},
{"group_name": "foo3", "options": "lalala3", "id":3},
{"group_name": "foo4", "options": "lalala4", "id":4},
]
class _DemoBase(object):
title = None
description = None
def get_grid():
raise NotImplementedError("subclass responsibility")
#### Demo classes ###
class BasicDemo(_DemoBase):
name = "Tickets"
description = """\
This table shows a basic grid."""
def get_grid(self):
"""
basic demo
"""
g = Grid(test_data, columns=["_numbered","group_name","options"])
return g
#### Demo classes ###
class CustomColumnDemo(_DemoBase):
name = "CustomColumn"
description = """\
This table shows a grid with a customized column and header label."""
def get_grid(self):
"""
let's override how rows look like
subject is link
categories and status hold text based on param of item text , the
translations are dicts holding translation strings correlated with
integers from db, in this example
"""
def options_td(col_num, i, item):
# XXX This module can't depend on 'app_globals' or 'url' or
# external data. Define data within this method or class or
# in a base class.
# Could use HTML.a() instead of link_to().
u = url("/tickets/view", ticket_id=item["id"])
a = link_to(item["options"], u)
return HTML.td(a)
g = Grid(test_data, columns=["_numbered","group_name","options"])
g.labels["options"] = 'FOOBAAR'
g.column_formats["options"] = options_td
return g
class OrderShiftDemo(_DemoBase):
name = "OrderShift"
description = """\
This table shows a grid with order starting from 10."""
def get_grid(self):
"""
order direction demo
"""
g = Grid(test_data, columns=["_numbered","group_name","options"],
start_number=10
)
return g
class OrderingDirectionHeaderAwareDemo(_DemoBase):
name = "OrderDirectionHeaderAwareDemo"
description = """\
This table shows a grid that has a markup indicating order direction.
Options column has sorting set to "asc" """
def get_grid(self):
"""
order direction demo
"""
g = Grid(test_data, columns=["_numbered","group_name","options"],
order_column='options', order_direction='asc'
)
#enable ordering support
g.exclude_ordering = []
return g
list_data = [
[1,'a',3,'c',5],
[11,'aa',33,'cc',55],
[111,'aaa',333,'ccc',555]
]
class ListDemo(_DemoBase):
name = "List grid demo"
description = """\
This table shows a basic grid generated from lists - it has customized order of columns."""
def get_grid(self):
"""
basic demo
"""
g = ListGrid(list_data, columns=[1, 3, 2, 0],
column_labels=["One", "Three", "Two", "Zero"])
return g
demos = subclasses_only(_DemoBase, globals())
#demos = [BasicDemo, CustomColumnDemo]
#### Utility functions ####
def url(urlpath, **params):
# This should be a helper and I think it's defined somewhere but I
# can't think of where.
return urlpath + "?" + urllib.urlencode(params)
def write_file(dir, filename, content):
print "... writing '%s'" % filename
path = os.path.join(dir, filename)
f = open(path, "w")
f.write(content)
f.close()
#### Main routine ####
def main():
parser = optparse.OptionParser(usage=USAGE, description=DESCRIPTION)
opts, args = parser.parse_args()
if len(args) != 1:
parser.error("wrong number of command-line arguments")
dir = args[0]
if not os.path.exists(dir):
os.makedirs(dir)
print "Putting output in directory '%s'" % dir
write_file(dir, "demo.css", STYLESHEET)
for class_ in demos:
d = class_()
name = d.name or d.__class__.__name__
filename = name + ".html"
dic = {
"title": d.name or d.__class__.__name__.lower(),
"description": d.description,
"grid": d.get_grid(),
}
html = HTML_TEMPLATE % dic
write_file(dir, filename, html)
if __name__ == "__main__": main()
WebHelpers-1.3/webhelpers/html/render.py 0000664 0001750 0001750 00000030574 11373653355 017762 0 ustar sluggo sluggo # This is a private implementation module. Import render() and sanitize()
# from webhelpers.html.converters .
# Contributed by Ian Bicking, downloaded from
# http://svn.w4py.org/ZPTKit/trunk/ZPTKit/htmlrender.py
# (Webware for Python framework)
#
# Changed by Mike Orr:
# - Add ``render()`` docstring. Export only that function by default.
# - Add ``sanitize()`` and ``HTMLSanitizer.
# - Change code when run as a script.
# - Don't convert Unicode to text.
# - Drop textwrap backport. (WebHelpers doesn't support Python < 2.3.)
##########################################################################
#
# Copyright (c) 2005 Imaginary Landscape LLC and Contributors.
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
##########################################################################
"""An HTML-to-text formatter and HTML sanitizer.
"""
from HTMLParser import HTMLParser
import htmlentitydefs
import re
import textwrap
__all__ = ["render", "sanitize"]
#### Public
def render(html, width=70):
"""Render HTML as formatted text, like lynx's "print" function.
Paragraphs are collected and wrapped at the specified width, default 70.
Blockquotes are indented. Paragraphs cannot be nested at this time. HTML
entities are substituted.
The formatting is simplistic, and works best with HTML that was written
for this renderer (e.g., ZPTKit.emailtemplate).
The output usually ends with two newlines. If the input ends with a close
tag plus any whitespace, the output ends with four newlines. This is
probably a bug.
"""
context = Context()
context.width = width
context.indent = 0
p = HTMLRenderer()
p.feed(html)
p.close()
paras = [para.to_text(context)
for para in p.paragraphs
if para]
return "".join(paras)
def sanitize(html):
"""Strip all HTML tags but leave their content.
Use this to strip any potentially malicious tags from user input.
HTML entities are left as-is.
Usage::
>>> sanitize(u'I really like steak!')
u'I really like steak!'
>>> sanitize(u'I really like steak!')
u'I really like NEFARIOUS CODE steak!'
"""
p = HTMLSanitizer()
p.feed(html)
p.close()
return "".join(p.output_chunks)
#### Private (though safe to use)
class HTMLRenderer(HTMLParser):
block_tags = 'p div blockquote h1 h2 h3 h4 h5 h6 ul ol'.split()
def reset(self):
HTMLParser.reset(self)
self.paragraphs = []
self.in_paragraph = None
self.last_href = None
self.href_content = None
self.in_table = None
self.cell_content = None
self.list_type = []
def handle_starttag(self, tag, attrs):
tag = tag.lower()
if tag == 'body':
self.paragraphs = []
self.in_paragraph = None
if tag == 'blockquote':
self.paragraphs.append(Indenter(4))
if tag in self.block_tags:
self.start_para(tag, attrs)
if tag == 'br':
self.add_br(tag, attrs)
if tag == 'a':
self.last_href = self.get_attr(attrs, 'href')
self.href_content = []
if tag == 'img':
alt = self.get_attr(attrs, 'alt')
if alt:
self.handle_data(alt)
if tag == 'table':
# @@: This is a hacky way of dealing with nested
# tables. Basically the tables are crudely flattened,
# and we keep track of how many
's we see based
# on the table.depth attribute (so we can later remove
# the table when sufficient
's have been seen)
if self.in_table:
self.in_table.depth += 1
else:
self.in_table = Table()
if tag == 'tr':
self.in_table.add_row()
if tag == 'td':
self.cell_content = []
if tag == 'ul':
self.paragraphs.append(Indenter(2))
self.list_type.append('ul')
if tag == 'ol':
self.paragraphs.append(Indenter(2))
self.list_type.append(1)
if tag == 'li':
self.add_br(None, None)
if not self.list_type or self.list_type[-1] == 'ul':
self.handle_data('* ')
else:
self.handle_data('%i) ' % self.list_type[-1])
self.list_type[-1] += 1
def handle_endtag(self, tag):
if tag in self.block_tags:
self.end_para(tag)
if tag == 'a':
if self.href_content:
content = ''.join(self.href_content)
else:
content = None
self.href_content = None
if content and self.last_href and content != self.last_href:
self.handle_data(' <%s>' % self.last_href)
self.last_href = None
if tag == 'table':
self.paragraphs.append(self.in_table)
self.in_table.depth -= 1
if not self.in_table.depth:
self.in_table = None
if tag == 'td':
self.end_para(tag)
if self.paragraphs:
self.in_table.add_cell(self.paragraphs[-1])
self.paragraphs.pop()
if tag == 'ul' or tag == 'ol':
self.paragraphs.append(Indenter(-2))
self.list_type.pop()
if tag == 'blockquote':
self.paragraphs.append(Indenter(-4))
def handle_data(self, data):
if self.in_paragraph is None:
self.start_para(None, None)
self.in_paragraph.add_text(data)
if self.href_content is not None:
self.href_content.append(data)
def handle_entityref(self, name):
name = name.lower()
if name not in htmlentitydefs.entitydefs:
# bad entity, just let it through
# (like a &var=value in a URL)
self.handle_data('&'+name)
return
result = htmlentitydefs.entitydefs[name]
if result.startswith('&'):
self.handle_charref(result[2:-1])
else:
self.handle_data(result)
def handle_charref(self, name):
try:
self.handle_data(unichr(int(name)))
except ValueError:
self.handle_data('&' + name)
def start_para(self, tag, attrs):
if tag is None:
# Implicit paragraph
tag = 'p'
attrs = []
self.end_para(None)
self.in_paragraph = Paragraph(tag, attrs)
def end_para(self, tag):
if self.in_paragraph:
self.paragraphs.append(self.in_paragraph)
self.in_paragraph = None
def add_br(self, tag, attrs):
if not self.in_paragraph:
self.start_para(None, None)
self.in_paragraph.add_tag(' ')
def close(self):
HTMLParser.close(self)
self.end_para(None)
def get_attr(self, attrs, name, default=None):
for attr_name, value in attrs:
if attr_name.lower() == name.lower():
return value
return default
class Paragraph:
def __init__(self, tag, attrs):
self.tag = tag
self.attrs = attrs
self.text = []
self._default_align = 'left'
def __repr__(self):
length = len(''.join(map(str, self.text)))
attrs = ' '.join([self.tag] +
['%s="%s"' % (name, value)
for name, value in self.attrs] +
['length=%i' % length])
return '' % (hex(id(self))[2:], attrs)
def add_text(self, text):
self.text.append(text)
def add_tag(self, tag):
self.text.append([tag])
def to_text(self, context):
lines = self.make_lines()
width = context.width
indent = context.indent
wrapped_lines = []
for line in lines:
wrapped = textwrap.wrap(
line,
width,
replace_whitespace=True,
initial_indent=' '*indent,
subsequent_indent=' '*indent,
fix_sentence_endings=False,
break_long_words=False)
wrapped_lines.extend(wrapped)
if self.tag in ('h1', 'h2'):
self._default_align = 'center'
lines = self.align_lines(wrapped_lines, width)
text = '\n'.join(lines)
if self.tag in ('h1', 'h3'):
text = text.upper()
if self.tag == 'h4':
text = '*%s*' % text
return text + '\n\n'
def align_lines(self, lines, width):
if self.alignment() == 'right':
return [' '*(width-len(line))+line
for line in lines]
elif self.alignment() == 'center':
return [' '*((width-len(line))/2)+line
for line in lines]
elif self.alignment() == 'left':
return lines
else:
# Could be odd things like 'baseline'; treat it as normal
return lines
def make_lines(self):
lines = ['']
for data in self.text:
if isinstance(data, list):
tag = data[0]
if tag == ' ':
lines.append('')
else:
assert 0, "Unknown tag: %r" % tag
else:
lines[-1] = lines[-1] + data
return [normalize(line).strip()
for line in lines
if line]
def alignment(self):
for name, value in self.attrs:
if name.lower() == 'align':
return value.lower()
return self._default_align
def __nonzero__(self):
for t in self.text:
if t:
return True
return False
class Table:
def __init__(self):
self.rows = []
self.row_num = 0
self.depth = 1
def add_row(self):
self.row_num += 1
self.rows.append([])
def add_cell(self, value):
self.rows[-1].append(value)
def __nonzero__(self):
return not not self.rows
def to_text(self, context):
if self.rows and not self.rows[-1]:
# Get rid of blank last line
self.rows.pop()
if not self.rows:
return ''
headers = [p.to_text(context).strip() for p in self.rows.pop(0)]
context.indent += 4
lines = []
for row in self.rows:
for header, cell in zip(headers, row):
cell_text = cell.to_text(context).strip()
lines.append('%s: %s' % (header, cell_text))
lines.append('')
context.indent -= 4
return '\n'.join(lines) + '\n\n'
class Indenter:
def __init__(self, indent):
self.indent = indent
def to_text(self, context):
context.indent += self.indent
return ''
class Context:
pass
class HTMLSanitizer(HTMLParser):
def reset(self):
HTMLParser.reset(self)
self.output_chunks = []
def handle_data(self, data):
self.output_chunks.append(data)
def normalize(text):
text = re.sub(r'\s+', ' ', text)
# nbsp:
if not isinstance(text, unicode):
text = text.replace('\xa0', ' ')
return text
#### Main routine
def main():
import os
import sys
if len(sys.argv) > 1:
prog = os.path.basename(sys.argv[0])
sys.exit("usage: %s