├── .gitignore ├── LICENSE ├── MANIFEST.in ├── README.rst ├── bootstrap.py ├── buildout.cfg ├── docs ├── Makefile └── source │ ├── conf.py │ └── index.rst ├── setup.py └── src └── easytags ├── __init__.py ├── library.py ├── models.py ├── node.py ├── tests ├── __init__.py ├── test_library.py ├── test_node.py └── test_parser.py └── testsettings.py /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.pyc 3 | *.pyo 4 | .installed.cfg 5 | bin 6 | build 7 | develop-eggs 8 | dist 9 | docs/Makefile 10 | docs/make.bat 11 | docs/build 12 | downloads 13 | eggs 14 | parts 15 | src/*.egg-info 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 Vinicius Mendes 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | 3. Neither the name of this project nor the names of its contributors may be 15 | used to endorse or promote products derived from this software without 16 | specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | django-meio-easytags 2 | ==================== 3 | 4 | An easy way to create custom template tags for Django's templating system. 5 | 6 | Usage 7 | ===== 8 | 9 | Just instantiate EasyLibrary and register the renderer method as a template tag:: 10 | 11 | from easytags import EasyLibrary 12 | 13 | register = EasyLibrary() 14 | 15 | def sum(context, arg1, arg2, arg3=0): 16 | return int(arg1) + int(arg2) + int(arg3) 17 | 18 | register.easytag(sum) 19 | 20 | The EasyLibrary will take care of subclassing EasyNode that will take care of 21 | the template tag's parsing, resolving variable and inspecting if the args are 22 | ok with the renderer signature. In the example above, you can use 23 | the sum tag in any of the forms below:: 24 | 25 | {% sum "1" "2" %} 26 | {% sum "1" arg2="2" %} 27 | {% sum arg1="1" arg2="2" %} 28 | {% sum arg2="2" arg1="1" %} 29 | 30 | It's almost like calling methods in Python. 31 | 32 | In this example, ``arg3`` is optional and defaults to 0. So you may use or not this arg. 33 | 34 | -------------------------------------------------------------------------------- /bootstrap.py: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | # 3 | # Copyright (c) 2006 Zope Foundation and Contributors. 4 | # All Rights Reserved. 5 | # 6 | # This software is subject to the provisions of the Zope Public License, 7 | # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 8 | # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 9 | # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 10 | # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 11 | # FOR A PARTICULAR PURPOSE. 12 | # 13 | ############################################################################## 14 | """Bootstrap a buildout-based project 15 | 16 | Simply run this script in a directory containing a buildout.cfg. 17 | The script accepts buildout command-line options, so you can 18 | use the -c option to specify an alternate configuration file. 19 | """ 20 | 21 | import os 22 | import shutil 23 | import sys 24 | import tempfile 25 | import urllib 26 | import urllib2 27 | import subprocess 28 | from optparse import OptionParser 29 | 30 | if sys.platform == 'win32': 31 | def quote(c): 32 | if ' ' in c: 33 | return '"%s"' % c # work around spawn lamosity on windows 34 | else: 35 | return c 36 | else: 37 | quote = str 38 | 39 | # See zc.buildout.easy_install._has_broken_dash_S for motivation and comments. 40 | stdout, stderr = subprocess.Popen( 41 | [sys.executable, '-Sc', 42 | 'try:\n' 43 | ' import ConfigParser\n' 44 | 'except ImportError:\n' 45 | ' print 1\n' 46 | 'else:\n' 47 | ' print 0\n'], 48 | stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() 49 | has_broken_dash_S = bool(int(stdout.strip())) 50 | 51 | # In order to be more robust in the face of system Pythons, we want to 52 | # run without site-packages loaded. This is somewhat tricky, in 53 | # particular because Python 2.6's distutils imports site, so starting 54 | # with the -S flag is not sufficient. However, we'll start with that: 55 | if not has_broken_dash_S and 'site' in sys.modules: 56 | # We will restart with python -S. 57 | args = sys.argv[:] 58 | args[0:0] = [sys.executable, '-S'] 59 | args = map(quote, args) 60 | os.execv(sys.executable, args) 61 | # Now we are running with -S. We'll get the clean sys.path, import site 62 | # because distutils will do it later, and then reset the path and clean 63 | # out any namespace packages from site-packages that might have been 64 | # loaded by .pth files. 65 | clean_path = sys.path[:] 66 | site = __import__('site') 67 | sys.path[:] = clean_path 68 | for k, v in sys.modules.items(): 69 | if k in ('setuptools', 'pkg_resources') or ( 70 | hasattr(v, '__path__') and 71 | len(v.__path__) == 1 and 72 | not os.path.exists(os.path.join(v.__path__[0], '__init__.py'))): 73 | # This is a namespace package. Remove it. 74 | sys.modules.pop(k) 75 | 76 | is_jython = sys.platform.startswith('java') 77 | 78 | setuptools_source = 'http://peak.telecommunity.com/dist/ez_setup.py' 79 | distribute_source = 'http://python-distribute.org/distribute_setup.py' 80 | 81 | 82 | # parsing arguments 83 | def normalize_to_url(option, opt_str, value, parser): 84 | if value: 85 | if '://' not in value: # It doesn't smell like a URL. 86 | value = 'file://%s' % ( 87 | urllib.pathname2url( 88 | os.path.abspath(os.path.expanduser(value))),) 89 | if opt_str == '--download-base' and not value.endswith('/'): 90 | # Download base needs a trailing slash to make the world happy. 91 | value += '/' 92 | else: 93 | value = None 94 | name = opt_str[2:].replace('-', '_') 95 | setattr(parser.values, name, value) 96 | 97 | usage = '''\ 98 | [DESIRED PYTHON FOR BUILDOUT] bootstrap.py [options] 99 | 100 | Bootstraps a buildout-based project. 101 | 102 | Simply run this script in a directory containing a buildout.cfg, using the 103 | Python that you want bin/buildout to use. 104 | 105 | Note that by using --setup-source and --download-base to point to 106 | local resources, you can keep this script from going over the network. 107 | ''' 108 | 109 | parser = OptionParser(usage=usage) 110 | parser.add_option( 111 | "-v", "--version", 112 | dest="version", 113 | help="use a specific zc.buildout version" 114 | ) 115 | parser.add_option( 116 | "-d", "--distribute", 117 | action="store_true", 118 | dest="use_distribute", 119 | default=False, 120 | help="Use Distribute rather than Setuptools." 121 | ) 122 | parser.add_option( 123 | "--setup-source", 124 | action="callback", 125 | dest="setup_source", 126 | callback=normalize_to_url, 127 | nargs=1, 128 | type="string", 129 | help=( 130 | "Specify a URL or file location for the setup file. " 131 | "If you use Setuptools, this will default to " + 132 | setuptools_source + "; if you use Distribute, this " 133 | "will default to " + distribute_source + "." 134 | ) 135 | ) 136 | parser.add_option( 137 | "--download-base", 138 | action="callback", 139 | dest="download_base", 140 | callback=normalize_to_url, 141 | nargs=1, 142 | type="string", 143 | help=( 144 | "Specify a URL or directory for downloading " 145 | "zc.buildout and either Setuptools or Distribute. " 146 | "Defaults to PyPI." 147 | ) 148 | ) 149 | parser.add_option( 150 | "--eggs", 151 | help=( 152 | "Specify a directory for storing eggs. Defaults to " 153 | "a temporary directory that is deleted when the " 154 | "bootstrap script completes." 155 | ) 156 | ) 157 | parser.add_option( 158 | "-t", "--accept-buildout-test-releases", 159 | dest='accept_buildout_test_releases', 160 | action="store_true", 161 | default=False, 162 | help=( 163 | "Normally, if you do not specify a --version, the " 164 | "bootstrap script and buildout gets the newest " 165 | "*final* versions of zc.buildout and its recipes and " 166 | "extensions for you. If you use this flag, " 167 | "bootstrap and buildout will get the newest releases " 168 | "even if they are alphas or betas." 169 | ) 170 | ) 171 | parser.add_option( 172 | "-c", None, 173 | action="store", 174 | dest="config_file", 175 | help=( 176 | "Specify the path to the buildout configuration " 177 | "file to be used." 178 | ) 179 | ) 180 | 181 | options, args = parser.parse_args() 182 | 183 | # if -c was provided, we push it back into args for buildout's main function 184 | if options.config_file is not None: 185 | args += ['-c', options.config_file] 186 | 187 | if options.eggs: 188 | eggs_dir = os.path.abspath(os.path.expanduser(options.eggs)) 189 | else: 190 | eggs_dir = tempfile.mkdtemp() 191 | 192 | if options.setup_source is None: 193 | if options.use_distribute: 194 | options.setup_source = distribute_source 195 | else: 196 | options.setup_source = setuptools_source 197 | 198 | if options.accept_buildout_test_releases: 199 | args.append('buildout:accept-buildout-test-releases=true') 200 | args.append('bootstrap') 201 | 202 | try: 203 | import pkg_resources 204 | import setuptools # A flag. Sometimes pkg_resources is installed alone. 205 | if not hasattr(pkg_resources, '_distribute'): 206 | raise ImportError 207 | except ImportError: 208 | ez_code = urllib2.urlopen( 209 | options.setup_source).read().replace('\r\n', '\n') 210 | ez = {} 211 | exec ez_code in ez 212 | setup_args = dict(to_dir=eggs_dir, download_delay=0) 213 | if options.download_base: 214 | setup_args['download_base'] = options.download_base 215 | if options.use_distribute: 216 | setup_args['no_fake'] = True 217 | ez['use_setuptools'](**setup_args) 218 | if 'pkg_resources' in sys.modules: 219 | reload(sys.modules['pkg_resources']) 220 | import pkg_resources 221 | # This does not (always?) update the default working set. We will 222 | # do it. 223 | for path in sys.path: 224 | if path not in pkg_resources.working_set.entries: 225 | pkg_resources.working_set.add_entry(path) 226 | 227 | cmd = [quote(sys.executable), 228 | '-c', 229 | quote('from setuptools.command.easy_install import main; main()'), 230 | '-mqNxd', 231 | quote(eggs_dir)] 232 | 233 | if not has_broken_dash_S: 234 | cmd.insert(1, '-S') 235 | 236 | find_links = options.download_base 237 | if not find_links: 238 | find_links = os.environ.get('bootstrap-testing-find-links') 239 | if find_links: 240 | cmd.extend(['-f', quote(find_links)]) 241 | 242 | if options.use_distribute: 243 | setup_requirement = 'distribute' 244 | else: 245 | setup_requirement = 'setuptools' 246 | ws = pkg_resources.working_set 247 | setup_requirement_path = ws.find( 248 | pkg_resources.Requirement.parse(setup_requirement)).location 249 | env = dict( 250 | os.environ, 251 | PYTHONPATH=setup_requirement_path) 252 | 253 | requirement = 'zc.buildout' 254 | version = options.version 255 | if version is None and not options.accept_buildout_test_releases: 256 | # Figure out the most recent final version of zc.buildout. 257 | import setuptools.package_index 258 | _final_parts = '*final-', '*final' 259 | 260 | def _final_version(parsed_version): 261 | for part in parsed_version: 262 | if (part[:1] == '*') and (part not in _final_parts): 263 | return False 264 | return True 265 | index = setuptools.package_index.PackageIndex( 266 | search_path=[setup_requirement_path]) 267 | if find_links: 268 | index.add_find_links((find_links,)) 269 | req = pkg_resources.Requirement.parse(requirement) 270 | if index.obtain(req) is not None: 271 | best = [] 272 | bestv = None 273 | for dist in index[req.project_name]: 274 | distv = dist.parsed_version 275 | if _final_version(distv): 276 | if bestv is None or distv > bestv: 277 | best = [dist] 278 | bestv = distv 279 | elif distv == bestv: 280 | best.append(dist) 281 | if best: 282 | best.sort() 283 | version = best[-1].version 284 | if version: 285 | requirement = '=='.join((requirement, version)) 286 | cmd.append(requirement) 287 | 288 | if is_jython: 289 | import subprocess 290 | exitcode = subprocess.Popen(cmd, env=env).wait() 291 | else: # Windows prefers this, apparently; otherwise we would prefer subprocess 292 | exitcode = os.spawnle(*([os.P_WAIT, sys.executable] + cmd + [env])) 293 | if exitcode != 0: 294 | sys.stdout.flush() 295 | sys.stderr.flush() 296 | print ("An error occurred when trying to install zc.buildout. " 297 | "Look above this message for any errors that " 298 | "were output by easy_install.") 299 | sys.exit(exitcode) 300 | 301 | ws.add_entry(eggs_dir) 302 | ws.require(requirement) 303 | import zc.buildout.buildout 304 | zc.buildout.buildout.main(args) 305 | if not options.eggs: # clean up temporary egg directory 306 | shutil.rmtree(eggs_dir) 307 | -------------------------------------------------------------------------------- /buildout.cfg: -------------------------------------------------------------------------------- 1 | [buildout] 2 | parts = python django sphinx 3 | develop = . 4 | eggs = django-meio-easytags 5 | 6 | [versions] 7 | django = 1.1.2 8 | 9 | [python] 10 | recipe = zc.recipe.egg 11 | interpreter = python 12 | eggs = ${buildout:eggs} 13 | 14 | [django] 15 | recipe = djangorecipe 16 | project = easytags 17 | projectegg = easytags 18 | settings = testsettings 19 | test = easytags 20 | eggs = ${buildout:eggs} 21 | 22 | [sphinx] 23 | recipe = collective.recipe.sphinxbuilder 24 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 14 | 15 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest 16 | 17 | help: 18 | @echo "Please use \`make ' where is one of" 19 | @echo " html to make standalone HTML files" 20 | @echo " dirhtml to make HTML files named index.html in directories" 21 | @echo " singlehtml to make a single large HTML file" 22 | @echo " pickle to make pickle files" 23 | @echo " json to make JSON files" 24 | @echo " htmlhelp to make HTML files and a HTML help project" 25 | @echo " qthelp to make HTML files and a qthelp project" 26 | @echo " devhelp to make HTML files and a Devhelp project" 27 | @echo " epub to make an epub" 28 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 29 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 30 | @echo " text to make text files" 31 | @echo " man to make manual pages" 32 | @echo " changes to make an overview of all changed/added/deprecated items" 33 | @echo " linkcheck to check all external links for integrity" 34 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 35 | 36 | clean: 37 | -rm -rf $(BUILDDIR)/* 38 | 39 | html: 40 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 41 | @echo 42 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 43 | 44 | dirhtml: 45 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 46 | @echo 47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 48 | 49 | singlehtml: 50 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 51 | @echo 52 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 53 | 54 | pickle: 55 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 56 | @echo 57 | @echo "Build finished; now you can process the pickle files." 58 | 59 | json: 60 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 61 | @echo 62 | @echo "Build finished; now you can process the JSON files." 63 | 64 | htmlhelp: 65 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 66 | @echo 67 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 68 | ".hhp project file in $(BUILDDIR)/htmlhelp." 69 | 70 | qthelp: 71 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 72 | @echo 73 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 74 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 75 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/django-meio-easytags.qhcp" 76 | @echo "To view the help file:" 77 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/django-meio-easytags.qhc" 78 | 79 | devhelp: 80 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 81 | @echo 82 | @echo "Build finished." 83 | @echo "To view the help file:" 84 | @echo "# mkdir -p $$HOME/.local/share/devhelp/django-meio-easytags" 85 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/django-meio-easytags" 86 | @echo "# devhelp" 87 | 88 | epub: 89 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 90 | @echo 91 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 92 | 93 | latex: 94 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 95 | @echo 96 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 97 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 98 | "(use \`make latexpdf' here to do that automatically)." 99 | 100 | latexpdf: 101 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 102 | @echo "Running LaTeX files through pdflatex..." 103 | make -C $(BUILDDIR)/latex all-pdf 104 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 105 | 106 | text: 107 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 108 | @echo 109 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 110 | 111 | man: 112 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 113 | @echo 114 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 115 | 116 | changes: 117 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 118 | @echo 119 | @echo "The overview file is in $(BUILDDIR)/changes." 120 | 121 | linkcheck: 122 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 123 | @echo 124 | @echo "Link check complete; look for any errors in the above output " \ 125 | "or in $(BUILDDIR)/linkcheck/output.txt." 126 | 127 | doctest: 128 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 129 | @echo "Testing of doctests in the sources finished, look at the " \ 130 | "results in $(BUILDDIR)/doctest/output.txt." 131 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # django-meio-easytags documentation build configuration file, created by 4 | # sphinx-quickstart on Tue Feb 22 22:55:42 2011. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import sys, os 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | #sys.path.insert(0, os.path.abspath('.')) 20 | 21 | # -- General configuration ----------------------------------------------------- 22 | 23 | # If your documentation needs a minimal Sphinx version, state it here. 24 | #needs_sphinx = '1.0' 25 | 26 | # Add any Sphinx extension module names here, as strings. They can be extensions 27 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 28 | extensions = [] 29 | 30 | # Add any paths that contain templates here, relative to this directory. 31 | templates_path = ['_templates'] 32 | 33 | # The suffix of source filenames. 34 | source_suffix = '.rst' 35 | 36 | # The encoding of source files. 37 | #source_encoding = 'utf-8-sig' 38 | 39 | # The master toctree document. 40 | master_doc = 'index' 41 | 42 | # General information about the project. 43 | project = u'django-meio-easytags' 44 | copyright = u'2011, Vinicius Mendes' 45 | 46 | # The version info for the project you're documenting, acts as replacement for 47 | # |version| and |release|, also used in various other places throughout the 48 | # built documents. 49 | # 50 | # The short X.Y version. 51 | version = '0.7' 52 | # The full version, including alpha/beta/rc tags. 53 | release = '0.7' 54 | 55 | # The language for content autogenerated by Sphinx. Refer to documentation 56 | # for a list of supported languages. 57 | #language = None 58 | 59 | # There are two options for replacing |today|: either, you set today to some 60 | # non-false value, then it is used: 61 | #today = '' 62 | # Else, today_fmt is used as the format for a strftime call. 63 | #today_fmt = '%B %d, %Y' 64 | 65 | # List of patterns, relative to source directory, that match files and 66 | # directories to ignore when looking for source files. 67 | exclude_patterns = [] 68 | 69 | # The reST default role (used for this markup: `text`) to use for all documents. 70 | #default_role = None 71 | 72 | # If true, '()' will be appended to :func: etc. cross-reference text. 73 | #add_function_parentheses = True 74 | 75 | # If true, the current module name will be prepended to all description 76 | # unit titles (such as .. function::). 77 | #add_module_names = True 78 | 79 | # If true, sectionauthor and moduleauthor directives will be shown in the 80 | # output. They are ignored by default. 81 | #show_authors = False 82 | 83 | # The name of the Pygments (syntax highlighting) style to use. 84 | pygments_style = 'sphinx' 85 | 86 | # A list of ignored prefixes for module index sorting. 87 | #modindex_common_prefix = [] 88 | 89 | 90 | # -- Options for HTML output --------------------------------------------------- 91 | 92 | # The theme to use for HTML and HTML Help pages. See the documentation for 93 | # a list of builtin themes. 94 | html_theme = 'default' 95 | 96 | # Theme options are theme-specific and customize the look and feel of a theme 97 | # further. For a list of options available for each theme, see the 98 | # documentation. 99 | #html_theme_options = {} 100 | 101 | # Add any paths that contain custom themes here, relative to this directory. 102 | #html_theme_path = [] 103 | 104 | # The name for this set of Sphinx documents. If None, it defaults to 105 | # " v documentation". 106 | #html_title = None 107 | 108 | # A shorter title for the navigation bar. Default is the same as html_title. 109 | #html_short_title = None 110 | 111 | # The name of an image file (relative to this directory) to place at the top 112 | # of the sidebar. 113 | #html_logo = None 114 | 115 | # The name of an image file (within the static path) to use as favicon of the 116 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 117 | # pixels large. 118 | #html_favicon = None 119 | 120 | # Add any paths that contain custom static files (such as style sheets) here, 121 | # relative to this directory. They are copied after the builtin static files, 122 | # so a file named "default.css" will overwrite the builtin "default.css". 123 | html_static_path = ['_static'] 124 | 125 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 126 | # using the given strftime format. 127 | #html_last_updated_fmt = '%b %d, %Y' 128 | 129 | # If true, SmartyPants will be used to convert quotes and dashes to 130 | # typographically correct entities. 131 | #html_use_smartypants = True 132 | 133 | # Custom sidebar templates, maps document names to template names. 134 | #html_sidebars = {} 135 | 136 | # Additional templates that should be rendered to pages, maps page names to 137 | # template names. 138 | #html_additional_pages = {} 139 | 140 | # If false, no module index is generated. 141 | #html_domain_indices = True 142 | 143 | # If false, no index is generated. 144 | #html_use_index = True 145 | 146 | # If true, the index is split into individual pages for each letter. 147 | #html_split_index = False 148 | 149 | # If true, links to the reST sources are added to the pages. 150 | #html_show_sourcelink = True 151 | 152 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 153 | #html_show_sphinx = True 154 | 155 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 156 | #html_show_copyright = True 157 | 158 | # If true, an OpenSearch description file will be output, and all pages will 159 | # contain a tag referring to it. The value of this option must be the 160 | # base URL from which the finished HTML is served. 161 | #html_use_opensearch = '' 162 | 163 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 164 | #html_file_suffix = None 165 | 166 | # Output file base name for HTML help builder. 167 | htmlhelp_basename = 'django-meio-easytagsdoc' 168 | 169 | 170 | # -- Options for LaTeX output -------------------------------------------------- 171 | 172 | # The paper size ('letter' or 'a4'). 173 | #latex_paper_size = 'letter' 174 | 175 | # The font size ('10pt', '11pt' or '12pt'). 176 | #latex_font_size = '10pt' 177 | 178 | # Grouping the document tree into LaTeX files. List of tuples 179 | # (source start file, target name, title, author, documentclass [howto/manual]). 180 | latex_documents = [ 181 | ('index', 'django-meio-easytags.tex', u'django-meio-easytags Documentation', 182 | u'Vinicius Mendes', 'manual'), 183 | ] 184 | 185 | # The name of an image file (relative to this directory) to place at the top of 186 | # the title page. 187 | #latex_logo = None 188 | 189 | # For "manual" documents, if this is true, then toplevel headings are parts, 190 | # not chapters. 191 | #latex_use_parts = False 192 | 193 | # If true, show page references after internal links. 194 | #latex_show_pagerefs = False 195 | 196 | # If true, show URL addresses after external links. 197 | #latex_show_urls = False 198 | 199 | # Additional stuff for the LaTeX preamble. 200 | #latex_preamble = '' 201 | 202 | # Documents to append as an appendix to all manuals. 203 | #latex_appendices = [] 204 | 205 | # If false, no module index is generated. 206 | #latex_domain_indices = True 207 | 208 | 209 | # -- Options for manual page output -------------------------------------------- 210 | 211 | # One entry per manual page. List of tuples 212 | # (source start file, name, description, authors, manual section). 213 | man_pages = [ 214 | ('index', 'django-meio-easytags', u'django-meio-easytags Documentation', 215 | [u'Vinicius Mendes'], 1) 216 | ] 217 | 218 | 219 | # -- Options for Epub output --------------------------------------------------- 220 | 221 | # Bibliographic Dublin Core info. 222 | epub_title = u'django-meio-easytags' 223 | epub_author = u'Vinicius Mendes' 224 | epub_publisher = u'Vinicius Mendes' 225 | epub_copyright = u'2011, Vinicius Mendes' 226 | 227 | # The language of the text. It defaults to the language option 228 | # or en if the language is not set. 229 | #epub_language = '' 230 | 231 | # The scheme of the identifier. Typical schemes are ISBN or URL. 232 | #epub_scheme = '' 233 | 234 | # The unique identifier of the text. This can be a ISBN number 235 | # or the project homepage. 236 | #epub_identifier = '' 237 | 238 | # A unique identification for the text. 239 | #epub_uid = '' 240 | 241 | # HTML files that should be inserted before the pages created by sphinx. 242 | # The format is a list of tuples containing the path and title. 243 | #epub_pre_files = [] 244 | 245 | # HTML files shat should be inserted after the pages created by sphinx. 246 | # The format is a list of tuples containing the path and title. 247 | #epub_post_files = [] 248 | 249 | # A list of files that should not be packed into the epub file. 250 | #epub_exclude_files = [] 251 | 252 | # The depth of the table of contents in toc.ncx. 253 | #epub_tocdepth = 3 254 | 255 | # Allow duplicate toc entries. 256 | #epub_tocdup = True 257 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. django-meio-easytags documentation master file, created by 2 | sphinx-quickstart on Tue Feb 22 22:55:42 2011. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | django-meio-easytags 7 | ==================== 8 | 9 | An easy way to create custom template tags for Django's templating system. 10 | 11 | EasyNode will take care of the template tag's parsing, resolving variable 12 | and inspecting if the parameters are ok with the ``render_context`` signature. 13 | 14 | It's almost like calling methods in Python. 15 | 16 | Basic usage 17 | ----------- 18 | 19 | Just subclass the EasyNode and create the method ``render_context`` and register 20 | the template tag:: 21 | 22 | from django.template import Library 23 | from easytags.node import EasyNode 24 | 25 | register = Library() 26 | 27 | class BasicNode(EasyNode): 28 | 29 | def render_context(self, context): 30 | return 'basic template tag' 31 | 32 | register.tag('basic', BasicNode.parse) 33 | 34 | Then load your template tags in your template and use it:: 35 | 36 | {% load my_template_tags %} 37 | 38 | {% basic %} 39 | 40 | Ok. It's easy to do with built-in django's ``Node``. In fact this case uses 41 | almost nothing of the django-meio-easytags power. Let's take a look at a 42 | more complex example. 43 | 44 | EasyLibrary usage 45 | ----------------- 46 | 47 | The ``easytags.EasyLibrary`` is a subclass of Django's default ``django.template.Library``. 48 | It adds a method to create easy tags just defining a function very similar to the 49 | ``render_context`` above. Just do this:: 50 | 51 | from easytags import EasyLibrary 52 | 53 | register = EasyLibrary() 54 | 55 | def basic(context): 56 | return u'basic template tag' 57 | 58 | register.easytag(basic) 59 | 60 | That's it. We just created our template tag. Pay atention that our register is an instance 61 | of ``easytags.EasyLibrary``, not of ``django.template.Library``. 62 | 63 | Custom template tag name 64 | ........................ 65 | 66 | You may set a custom name for your template tag just like you do with ordinary ``register.tag``:: 67 | 68 | register.easytag('my_fancy_name', basic) 69 | 70 | register.easytag as decorator 71 | ............................. 72 | 73 | You can register easy tags with decorators:: 74 | 75 | @register.easytag 76 | def decorated(context): 77 | return u'decorated' 78 | 79 | And define the tag name inside the decorator:: 80 | 81 | @register.easytag(name='fancy_decorated') 82 | def decorated(context): 83 | return u'fancy decorated' 84 | 85 | Accepts parameters 86 | ------------------------------------------------ 87 | 88 | You can also create a template tag that receives one or more parameters and 89 | set default values to it like you do in Python methods:: 90 | 91 | from easytags import EasyLibrary 92 | 93 | register = EasyLibrary() 94 | 95 | def sum(context, arg1, arg2, arg3=0): 96 | return int(arg1) + int(arg2) + int(arg3) 97 | 98 | register.easytag(sum) 99 | 100 | In this case, your template tag have two mandatory parameters (``arg1`` and 101 | ``arg2``) and one optional parameter (``arg3``) that defaults to ``0``. You 102 | can use this template tag in any of these ways:: 103 | 104 | {% sum "1" "2" %} 105 | {% sum "1" arg2="2" %} 106 | {% sum arg1="1" arg2="2" %} 107 | {% sum "1" "2" "3" %} 108 | {% sum arg2="2" arg1="1 %} 109 | 110 | That is the way you are already used to do in Python. The django-meio-easytags 111 | parser will take care of verifying if your template tag accepts the parameters 112 | defined and if it doesn't, it will raise a ``TemplateSyntaxError``. You don't 113 | need to define anywere else which parameters your template tag accepts except 114 | for the ``render_context`` signature definition. 115 | 116 | Resolving context variable 117 | .......................... 118 | 119 | Pay atention that in the example above, all parameters are defined as absolute 120 | values, that is, inside double quotes. This is needed because django-meio-easytags 121 | allows you to create template tags that accepts context variables as default:: 122 | 123 | {% sum variable1 "2" %} 124 | 125 | The code above will resolve ``variable1`` in the context and use as first parameter 126 | for the ``sum`` template tag. 127 | 128 | Accepts *args 129 | ------------- 130 | 131 | There's a very nice feature in python that makes it possible to create methods 132 | that accepts infinite parameters. In django-meio-easytags it's possible too:: 133 | 134 | from easytags import EasyLibrary 135 | 136 | register = EasyLibrary() 137 | 138 | def join_lines(context, *args): 139 | return u'
'.join(args) 140 | 141 | register.easytag(join_lines) 142 | 143 | With this tag you can join as many lines as you want:: 144 | 145 | {% join_lines "line1" %} # Outputs "line1" 146 | {% join_lines "line1" "line2" %} # Outputs "line1
line2" 147 | {% join_lines "line1" "line2" "lineN" %} # Outputs "line1
line2
lineN" 148 | 149 | Accepts **kwargs 150 | ---------------- 151 | 152 | In python you may create methods that receives any named parameter and 153 | django-meio-easytags supports it too:: 154 | 155 | from easytags import EasyLibrary 156 | 157 | register = EasyLibrary() 158 | 159 | def querystring(context, **kwargs): 160 | return u'&'.join(u'%s=%s' % (k,v) for k, v in kwargs.items()) 161 | 162 | register.easytag(querystring) 163 | 164 | With this tag you can build a querystring defining each key and value:: 165 | 166 | {% querystring key1="1" key2="2" %} # Outputs "key1=1&key2=2 167 | 168 | Using `as` parameter 169 | -------------------- 170 | 171 | Thats a common usage in template tags construction to use a parameter to define a 172 | variable to receive the content generated by the rendering of the template tag. In 173 | django-meio-easytags you can use `EasyAsNode` to achieve such a behavior. Just 174 | override the `EasyAsNode` instead of `EasyNode` and use it in your templates like 175 | this:: 176 | 177 | from django.template import Library 178 | from easytags.node import EasyAsNode 179 | 180 | register = Library() 181 | 182 | class BasicAsNode(EasyAsNode): 183 | 184 | def render_context(self, context): 185 | return 'basic content to variable' 186 | 187 | register.tag('basic', BasicAsNode.parse) 188 | 189 | Then load your template tags in your template and use it:: 190 | 191 | {% load my_template_tags %} 192 | 193 | {% basic as varname %} 194 | 195 | And the Node will store the returning value from `render_context` in a context 196 | variable name `varname`. Nice, but what about all the library stuff? I can't use 197 | the `as` parameter and register it in EasyLibrary? Yes you can! Just create your 198 | method and register it as an `easyastag`:: 199 | 200 | from easytags import EasyLibrary 201 | 202 | register = EasyLibrary() 203 | 204 | def basic(context): 205 | return 'basic content to variable' 206 | 207 | register.easyastag(basic) 208 | 209 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from setuptools import setup, find_packages 4 | 5 | 6 | def read(fname): 7 | return open(os.path.join(os.path.dirname(__file__), fname)).read() 8 | 9 | 10 | setup( 11 | name="django-meio-easytags", 12 | version="0.7", 13 | url="http://www.meiocodigo.com/projects/django-meio-easytags/", 14 | license="BSD", 15 | description="An easy way to create custom template tags " + 16 | "for Django's templating system.", 17 | long_description=read('README.rst'), 18 | author="Vinicius Mendes", 19 | author_email="vbmendes@gmail.com", 20 | packages=find_packages('src'), 21 | package_dir={'': 'src'}, 22 | install_requires=['setuptools'], 23 | classifiers=[ 24 | 'Development Status :: 4 - Beta', 25 | 'Framework :: Django', 26 | 'Intended Audience :: Developers', 27 | 'License :: OSI Approved :: BSD License', 28 | 'Natural Language :: English', 29 | 'Operating System :: OS Independent', 30 | 'Programming Language :: Python', 31 | 'Topic :: Internet :: WWW/HTTP', 32 | 'Topic :: Software Development :: Libraries :: Python Modules', 33 | ] 34 | ) 35 | -------------------------------------------------------------------------------- /src/easytags/__init__.py: -------------------------------------------------------------------------------- 1 | from library import EasyLibrary 2 | from node import EasyNode, EasyAsNode 3 | -------------------------------------------------------------------------------- /src/easytags/library.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | ''' 4 | Created on 01/03/2011 5 | 6 | @author: vbmendes 7 | ''' 8 | 9 | from django.template import Library 10 | 11 | from node import EasyNode, EasyAsNode 12 | 13 | 14 | class EasyLibrary(Library): 15 | 16 | @classmethod 17 | def _get_name_and_renderer(cls, name, renderer): 18 | if not renderer: 19 | renderer = name 20 | name = renderer.__name__ 21 | return name, renderer 22 | 23 | def easytag(self, name=None, renderer=None): 24 | return self._handle_decorator(EasyNode, name, renderer) 25 | 26 | def easyastag(self, name=None, renderer=None): 27 | return self._handle_decorator(EasyAsNode, name, renderer) 28 | 29 | def _handle_decorator(self, node_class, name, renderer): 30 | if not name and not renderer: 31 | return self.easytag 32 | if not renderer: 33 | if callable(name): 34 | renderer = name 35 | return self._register_easytag(node_class, renderer.__name__, renderer) 36 | else: 37 | def dec(renderer): 38 | return self._register_easytag(node_class, name, renderer) 39 | return dec 40 | return self._register_easytag(node_class, name, renderer) 41 | 42 | def _register_easytag(self, node_class, name, renderer): 43 | if not renderer: 44 | renderer = name 45 | name = renderer.__name__ 46 | 47 | def render_context(self, context, *args, **kwargs): 48 | return renderer(context, *args, **kwargs) 49 | 50 | get_argspec = classmethod(lambda cls: node_class.get_argspec(renderer)) 51 | 52 | tag_node = type('%sEasyNode' % name, (node_class,), { 53 | 'render_context': render_context, 54 | 'get_argspec': get_argspec, 55 | }) 56 | self.tag(name, tag_node.parse) 57 | return renderer 58 | -------------------------------------------------------------------------------- /src/easytags/models.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbmendes/django-meio-easytags/817a82423705021bdfeedf6582978b3475382043/src/easytags/models.py -------------------------------------------------------------------------------- /src/easytags/node.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | ''' 4 | Created on 20/02/2011 5 | 6 | @author: vbmendes 7 | ''' 8 | 9 | from inspect import getargspec 10 | 11 | from django.template import Node, Variable, TemplateSyntaxError 12 | 13 | 14 | is_kwarg = lambda bit: not bit[0] in (u'"', u"'") and u'=' in bit 15 | 16 | 17 | def get_args_kwargs_from_bits(parser, bits): 18 | args = [] 19 | kwargs = {} 20 | for bit in bits: 21 | if is_kwarg(bit): 22 | key, value = bit.split(u'=', 1) 23 | kwargs[key] = parser.compile_filter(value) 24 | else: 25 | if not kwargs: 26 | args.append(parser.compile_filter(bit)) 27 | else: 28 | raise TemplateSyntaxError(u"Args must be before kwargs.") 29 | 30 | return {'args': tuple(args), 'kwargs': kwargs} 31 | 32 | 33 | def SmartVariable(var): 34 | if hasattr(var, 'resolve'): 35 | return var 36 | return Variable(var) 37 | 38 | 39 | class EasyNode(Node): 40 | 41 | @classmethod 42 | def parse_to_args_kwargs(cls, parser, token): 43 | bits = token.split_contents() 44 | return get_args_kwargs_from_bits(parser, bits[1:]) 45 | 46 | @classmethod 47 | def parse(cls, parser, token): 48 | args_kwargs = cls.parse_to_args_kwargs(parser, token) 49 | cls.is_args_kwargs_valid(args_kwargs) 50 | return cls(args_kwargs) 51 | 52 | @classmethod 53 | def get_argspec(cls, func=None): 54 | func = func or cls.render_context 55 | return getargspec(func) 56 | 57 | @classmethod 58 | def is_args_kwargs_valid(cls, args_kwargs): 59 | render_context_spec = cls.get_argspec() 60 | 61 | args = args_kwargs['args'] 62 | kwargs = args_kwargs['kwargs'] 63 | 64 | valid_args_names = render_context_spec.args 65 | if 'self' in valid_args_names: 66 | valid_args_names.remove('self') 67 | if 'context' in valid_args_names: 68 | valid_args_names.remove('context') 69 | 70 | n_args_kwargs = len(args) + len(kwargs) 71 | 72 | max_n_args_kwargs = len(valid_args_names) 73 | if not render_context_spec.varargs and not render_context_spec.keywords and n_args_kwargs > max_n_args_kwargs: 74 | raise TemplateSyntaxError(u'Invalid number of args %s (max. %s)' % (n_args_kwargs, max_n_args_kwargs)) 75 | 76 | min_n_args_kwargs = max_n_args_kwargs - len(render_context_spec.defaults or ()) 77 | if n_args_kwargs < min_n_args_kwargs: 78 | raise TemplateSyntaxError(u'Invalid number of args %s (min. %s)' % (n_args_kwargs, max_n_args_kwargs)) 79 | 80 | required_args_names = valid_args_names[len(args):min_n_args_kwargs] 81 | for required_arg_name in required_args_names: 82 | if not required_arg_name in kwargs: 83 | raise TemplateSyntaxError(u'Required arg missing: %s' % required_arg_name) 84 | 85 | first_kwarg_index = len(args) 86 | if not render_context_spec.keywords: 87 | valid_kwargs = valid_args_names[first_kwarg_index:] 88 | for kwarg in kwargs: 89 | if not kwarg in valid_kwargs: 90 | raise TemplateSyntaxError(u'Invalid kwarg %s.' % kwarg) 91 | else: 92 | defined_args = valid_args_names[:first_kwarg_index] 93 | for kwarg in kwargs: 94 | if kwarg in defined_args: 95 | raise TemplateSyntaxError(u'%s was defined twice.' % kwarg) 96 | 97 | def __init__(self, args_kwargs): 98 | self.args = [SmartVariable(arg) for arg in args_kwargs['args']] 99 | self.kwargs = dict((key, SmartVariable(value)) for key, value in args_kwargs['kwargs'].iteritems()) 100 | 101 | def render(self, context): 102 | args = [arg.resolve(context) for arg in self.args] 103 | kwargs = dict((str(key), value.resolve(context)) for key, value in self.kwargs.iteritems()) 104 | return self.render_context(context, *args, **kwargs) 105 | 106 | def render_context(self, context, *args, **kwargs): 107 | raise NotImplementedError 108 | 109 | 110 | class EasyAsNode(EasyNode): 111 | 112 | @classmethod 113 | def parse_to_args_kwargs(cls, parser, token): 114 | bits = token.split_contents()[1:] 115 | if len(bits) >= 2 and bits[-2] == 'as': 116 | varname = bits[-1] 117 | bits = bits[:-2] 118 | else: 119 | varname = None 120 | args_kwargs = get_args_kwargs_from_bits(parser, bits) 121 | args_kwargs['varname'] = varname 122 | return args_kwargs 123 | 124 | def __init__(self, args_kwargs): 125 | super(EasyAsNode, self).__init__(args_kwargs) 126 | self.varname = args_kwargs['varname'] 127 | 128 | def render(self, context): 129 | rendered = super(EasyAsNode, self).render(context) 130 | if self.varname: 131 | context[self.varname] = rendered 132 | return u'' 133 | return rendered 134 | -------------------------------------------------------------------------------- /src/easytags/tests/__init__.py: -------------------------------------------------------------------------------- 1 | from test_parser import ParserTests 2 | from test_library import LibraryTests 3 | from test_node import NodeTests 4 | -------------------------------------------------------------------------------- /src/easytags/tests/test_library.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | ''' 4 | Created on 01/03/2011 5 | 6 | @author: vbmendes 7 | ''' 8 | 9 | import unittest 10 | 11 | from django import template 12 | 13 | from easytags import EasyLibrary 14 | from easytags import EasyNode, EasyAsNode 15 | 16 | 17 | class LibraryTests(unittest.TestCase): 18 | 19 | def test_easy_library_register_easy_node(self): 20 | def test_tag(context): 21 | return u'my return' 22 | 23 | register = EasyLibrary() 24 | register.easytag(test_tag) 25 | 26 | parser = template.Parser([]) 27 | token = template.Token(template.TOKEN_BLOCK, 'test_tag') 28 | 29 | self.assertTrue('test_tag' in register.tags) 30 | 31 | test_node = register.tags['test_tag'](parser, token) 32 | 33 | self.assertTrue(isinstance(test_node, EasyNode)) 34 | 35 | context = template.Context({}) 36 | 37 | self.assertEquals(u'my return', test_node.render(context)) 38 | 39 | def test_easy_library_register_easy_node_with_parameters(self): 40 | def test_tag(context, arg1): 41 | return arg1 42 | 43 | register = EasyLibrary() 44 | register.easytag(test_tag) 45 | 46 | parser = template.Parser([]) 47 | token = template.Token(template.TOKEN_BLOCK, 'test_tag "my arg"') 48 | test_node = register.tags['test_tag'](parser, token) 49 | 50 | context = template.Context({}) 51 | self.assertEquals(u'my arg', test_node.render(context)) 52 | 53 | def test_easy_library_register_tags_with_custom_names(self): 54 | def test_tag(context): 55 | return u'' 56 | 57 | register = EasyLibrary() 58 | register.easytag('tag_name', test_tag) 59 | 60 | self.assertTrue('tag_name' in register.tags) 61 | 62 | def test_easy_library_register_tags_as_decorating_method(self): 63 | def test_tag(context): 64 | return u'' 65 | 66 | register = EasyLibrary() 67 | register.easytag()(test_tag) 68 | 69 | self.assertTrue('test_tag' in register.tags) 70 | 71 | def test_easy_library_register_tags_as_decorating_method_with_name(self): 72 | def test_tag(context): 73 | return u'' 74 | 75 | register = EasyLibrary() 76 | register.easytag('tag_name')(test_tag) 77 | 78 | self.assertTrue('tag_name' in register.tags) 79 | 80 | def test_easy_library_register_tags_as_decorating_method_with_name_kwarg(self): 81 | def test_tag(context): 82 | return u'' 83 | 84 | register = EasyLibrary() 85 | register.easytag(name='tag_name')(test_tag) 86 | 87 | self.assertTrue('tag_name' in register.tags) 88 | 89 | def test_easy_library_register_tags_keeps_decorated_function_data(self): 90 | def test_tag(context): 91 | return u'' 92 | 93 | register = EasyLibrary() 94 | test_tag = register.easytag(name='tag_name')(test_tag) 95 | 96 | self.assertEquals('test_tag', test_tag.__name__) 97 | 98 | def test_easy_library_register_as_tags(self): 99 | def test_tag(context): 100 | return u'my return' 101 | 102 | register = EasyLibrary() 103 | register.easyastag(test_tag) 104 | 105 | parser = template.Parser([]) 106 | token = template.Token(template.TOKEN_BLOCK, 'test_tag as varname') 107 | 108 | self.assertTrue('test_tag' in register.tags) 109 | 110 | test_node = register.tags['test_tag'](parser, token) 111 | 112 | self.assertTrue(isinstance(test_node, EasyAsNode)) 113 | 114 | context = template.Context({}) 115 | 116 | self.assertEquals(u'', test_node.render(context)) 117 | self.assertEquals(u'my return', context['varname']) 118 | -------------------------------------------------------------------------------- /src/easytags/tests/test_node.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | ''' 4 | Created on 20/02/2011 5 | 6 | @author: vbmendes 7 | ''' 8 | 9 | from django import template 10 | from django.template import Context, Variable, TemplateSyntaxError 11 | from django.test import TestCase 12 | 13 | from easytags.node import EasyNode, EasyAsNode 14 | 15 | 16 | class MyEasyNode(EasyNode): 17 | 18 | def render_context(self, context, arg1, kwarg1=None): 19 | return arg1 20 | 21 | 22 | class MyEasyNodeWithoutDefaults(EasyNode): 23 | 24 | def render_context(self, context, arg1): 25 | return arg1 26 | 27 | 28 | class NodeTests(TestCase): 29 | 30 | def test_resolves_absolute_string(self): 31 | context = Context({}) 32 | args_kwargs = {'args': ('"absolute string"',), 'kwargs': {}} 33 | 34 | node = MyEasyNode(args_kwargs) 35 | 36 | self.assertEquals( 37 | u'absolute string', 38 | node.render(context), 39 | ) 40 | 41 | def test_resolve_simple_variable(self): 42 | context = Context({'simple_variable': u'simple variable value'}) 43 | args_kwargs = {'args': ('simple_variable',), 'kwargs': {}} 44 | 45 | node = MyEasyNode(args_kwargs) 46 | 47 | self.assertEquals( 48 | u'simple variable value', 49 | node.render(context), 50 | ) 51 | 52 | def test_resolve_dict_variable(self): 53 | context = Context({'mydict': {'key': u'value'}}) 54 | args_kwargs = {'args': ('mydict.key',), 'kwargs': {}} 55 | 56 | node = MyEasyNode(args_kwargs) 57 | 58 | self.assertEquals( 59 | u'value', 60 | node.render(context), 61 | ) 62 | 63 | def test_resolve_absolute_string_in_kwargs(self): 64 | context = Context({}) 65 | args_kwargs = {'args': (), 'kwargs': {'arg1': u'"absolute string"'}} 66 | 67 | node = MyEasyNode(args_kwargs) 68 | 69 | self.assertEquals( 70 | u'absolute string', 71 | node.render(context), 72 | ) 73 | 74 | def test_resolve_simple_variable_in_kwargs(self): 75 | context = Context({'simple_variable': u'simple variable value'}) 76 | args_kwargs = {'args': (), 'kwargs': {'arg1': u'simple_variable'}} 77 | 78 | node = MyEasyNode(args_kwargs) 79 | 80 | self.assertEquals( 81 | u'simple variable value', 82 | node.render(context), 83 | ) 84 | 85 | def test_resolve_dict_variable_in_kwargs(self): 86 | context = Context({'mydict': {'key': u'value'}}) 87 | args_kwargs = {'args': (), 'kwargs': {'arg1': 'mydict.key'}} 88 | 89 | node = MyEasyNode(args_kwargs) 90 | 91 | self.assertEquals( 92 | u'value', 93 | node.render(context), 94 | ) 95 | 96 | def test_node_parse_returns_node_instance(self): 97 | parser = template.Parser([]) 98 | token = template.Token(template.TOKEN_BLOCK, 'tag_name arg1 kwarg1="a=1"') 99 | node = MyEasyNode.parse(parser, token) 100 | 101 | self.assertTrue(isinstance(node, MyEasyNode)) 102 | self.assertEquals(u'arg1', node.args[0].token) 103 | self.assertEquals(u'"a=1"', node.kwargs['kwarg1'].token) 104 | 105 | def test_node_parse_verifies_invalid_kwarg(self): 106 | parser = template.Parser([]) 107 | token = template.Token(template.TOKEN_BLOCK, 'tag_name arg1 invalid_kwarg="a=1"') 108 | 109 | self.assertRaises(TemplateSyntaxError, MyEasyNode.parse, parser, token) 110 | 111 | def test_node_parse_verifies_kwarg_already_satisfied_by_arg(self): 112 | parser = template.Parser([]) 113 | token = template.Token(template.TOKEN_BLOCK, 'tag_name arg1 arg1="a=1"') 114 | 115 | self.assertRaises(TemplateSyntaxError, MyEasyNode.parse, parser, token) 116 | 117 | def test_node_parse_verifies_if_there_are_more_args_kwargs_then_method_requires(self): 118 | parser = template.Parser([]) 119 | token = template.Token(template.TOKEN_BLOCK, 'tag_name arg1 arg2 arg3') 120 | 121 | self.assertRaises(TemplateSyntaxError, MyEasyNode.parse, parser, token) 122 | 123 | def test_node_parse_verifies_if_there_are_less_args_kwargs_then_method_requires(self): 124 | parser = template.Parser([]) 125 | token = template.Token(template.TOKEN_BLOCK, 'tag_name') 126 | 127 | self.assertRaises(TemplateSyntaxError, MyEasyNode.parse, parser, token) 128 | 129 | def test_node_parse_verifies_if_required_arg_is_specified(self): 130 | parser = template.Parser([]) 131 | token = template.Token(template.TOKEN_BLOCK, 'tag_name kwarg1="a"') 132 | 133 | self.assertRaises(TemplateSyntaxError, MyEasyNode.parse, parser, token) 134 | 135 | def test_node_can_have_no_args_with_default_value(self): 136 | parser = template.Parser([]) 137 | token = template.Token(template.TOKEN_BLOCK, 'tag_name "a"') 138 | 139 | node = MyEasyNodeWithoutDefaults.parse(parser, token) 140 | 141 | self.assertEquals(u'a' ,node.args[0].var) 142 | 143 | def test_node_can_receive_infinite_args(self): 144 | parser = template.Parser([]) 145 | token = template.Token(template.TOKEN_BLOCK, 'tag_name "a" "b" "c" "d"') 146 | 147 | MyEasyNode = type('MyEasyNodeWithArgs', (EasyNode,), { 148 | 'render_context': lambda self, context, *args: reduce(lambda x, y: x + y, args) 149 | }) 150 | 151 | node = MyEasyNode.parse(parser, token) 152 | 153 | self.assertEquals(u'abcd' ,node.render(Context({}))) 154 | 155 | def test_node_can_receive_required_arg_and_infinite_args(self): 156 | parser = template.Parser([]) 157 | token = template.Token(template.TOKEN_BLOCK, 'tag_name "a" "b" "c" "d"') 158 | 159 | MyEasyNode = type('MyEasyNode', (EasyNode,), { 160 | 'render_context': lambda self, context, arg1, *args: arg1 + reduce(lambda x, y: x + y, args) 161 | }) 162 | 163 | node = MyEasyNode.parse(parser, token) 164 | 165 | self.assertEquals(u'abcd' ,node.render(Context({}))) 166 | 167 | def test_node_verifies_if_required_arg_is_specified_when_node_can_receive_infinite_args(self): 168 | parser = template.Parser([]) 169 | token = template.Token(template.TOKEN_BLOCK, 'tag_name') 170 | 171 | MyEasyNode = type('MyEasyNode', (EasyNode,), { 172 | 'render_context': lambda self, context, arg1, *args: True 173 | }) 174 | 175 | self.assertRaises(TemplateSyntaxError, MyEasyNode.parse, parser, token) 176 | 177 | def test_node_can_receive_kwargs(self): 178 | parser = template.Parser([]) 179 | token = template.Token(template.TOKEN_BLOCK, 'tag_name arg1="bla" arg2="ble"') 180 | 181 | MyEasyNode = type('MyEasyNode', (EasyNode,), { 182 | 'render_context': lambda self, context, **kwargs:\ 183 | reduce(lambda x,y: u'%s%s' % (x, y), 184 | ['%s=%s' % (key, value) for key, value in kwargs.items()]) 185 | }) 186 | 187 | node = MyEasyNode.parse(parser, token) 188 | 189 | self.assertEquals(u'arg1=blaarg2=ble', node.render(Context({}))) 190 | 191 | def test_node_verifies_if_required_arg_is_specified_when_code_can_receive_kwargs(self): 192 | parser = template.Parser([]) 193 | token = template.Token(template.TOKEN_BLOCK, 'tag_name') 194 | 195 | MyEasyNode = type('MyEasyNode', (EasyNode,), { 196 | 'render_context': lambda self, context, arg1, **kwargs: True 197 | }) 198 | 199 | self.assertRaises(TemplateSyntaxError, MyEasyNode.parse, parser, token) 200 | 201 | def test_node_verifies_if_required_kwarg_is_specified_when_code_can_receive_kwargs(self): 202 | parser = template.Parser([]) 203 | token = template.Token(template.TOKEN_BLOCK, 'tag_name arg2="2"') 204 | 205 | MyEasyNode = type('MyEasyNode', (EasyNode,), { 206 | 'render_context': lambda self, context, arg1, **kwargs: True 207 | }) 208 | 209 | self.assertRaises(TemplateSyntaxError, MyEasyNode.parse, parser, token) 210 | 211 | def test_if_node_can_receive_args_and_kwargs(self): 212 | parser = template.Parser([]) 213 | token = template.Token(template.TOKEN_BLOCK, 'tag_name "1" arg2="2"') 214 | 215 | MyEasyNode = type('MyEasyNode', (EasyNode,), { 216 | 'render_context': lambda self, context, *args, **kwargs: 217 | args[0]+kwargs.items()[0][0]+u'='+kwargs.items()[0][1] 218 | }) 219 | 220 | node = MyEasyNode.parse(parser, token) 221 | 222 | self.assertEquals(u'1arg2=2', node.render(Context({}))) 223 | 224 | def test_if_node_can_receive_required_arg_and_kwargs(self): 225 | parser = template.Parser([]) 226 | token = template.Token(template.TOKEN_BLOCK, 'tag_name "required" "2" arg3="3"') 227 | 228 | MyEasyNode = type('MyEasyNode', (EasyNode,), { 229 | 'render_context': lambda self, context, arg1, *args, **kwargs: 230 | arg1+args[0]+kwargs.items()[0][0]+u'='+kwargs.items()[0][1] 231 | }) 232 | 233 | node = MyEasyNode.parse(parser, token) 234 | 235 | self.assertEquals(u'required2arg3=3', node.render(Context({}))) 236 | 237 | def test_node_verifies_if_required_arg_is_specified_two_times(self): 238 | parser = template.Parser([]) 239 | token = template.Token(template.TOKEN_BLOCK, 'tag_name "required" arg1="3"') 240 | 241 | MyEasyNode = type('MyEasyNode', (EasyNode,), { 242 | 'render_context': lambda self, context, arg1, *args, **kwargs: True 243 | }) 244 | 245 | self.assertRaises(TemplateSyntaxError, MyEasyNode.parse, parser, token) 246 | 247 | def test_node_applies_filters_to_args(self): 248 | parser = template.Parser([]) 249 | context = Context({}) 250 | token = template.Token(template.TOKEN_BLOCK, 'tag_name "string1 string2"|slugify|upper') 251 | args_kwargs = EasyNode.parse_to_args_kwargs(parser, token) 252 | 253 | node = MyEasyNode(args_kwargs) 254 | 255 | self.assertEquals( 256 | u'STRING1-STRING2', 257 | node.render(context), 258 | ) 259 | 260 | def test_node_applies_filters_to_kwargs(self): 261 | parser = template.Parser([]) 262 | context = Context({}) 263 | token = template.Token(template.TOKEN_BLOCK, 'tag_name arg1="string1 string2"|slugify|upper') 264 | args_kwargs = EasyNode.parse_to_args_kwargs(parser, token) 265 | 266 | node = MyEasyNode(args_kwargs) 267 | 268 | self.assertEquals( 269 | u'STRING1-STRING2', 270 | node.render(context), 271 | ) 272 | 273 | def test_node_applies_filters_to_variable_in_args(self): 274 | parser = template.Parser([]) 275 | context = Context({'variable': "string1 string2"}) 276 | token = template.Token(template.TOKEN_BLOCK, 'tag_name variable|slugify|upper') 277 | args_kwargs = EasyNode.parse_to_args_kwargs(parser, token) 278 | 279 | node = MyEasyNode(args_kwargs) 280 | 281 | self.assertEquals( 282 | u'STRING1-STRING2', 283 | node.render(context), 284 | ) 285 | 286 | def test_node_applies_filters_to_variable_in_kwargs(self): 287 | parser = template.Parser([]) 288 | context = Context({'variable': "string1 string2"}) 289 | token = template.Token(template.TOKEN_BLOCK, 'tag_name arg1=variable|slugify|upper') 290 | args_kwargs = EasyNode.parse_to_args_kwargs(parser, token) 291 | 292 | node = MyEasyNode(args_kwargs) 293 | 294 | self.assertEquals( 295 | u'STRING1-STRING2', 296 | node.render(context), 297 | ) 298 | 299 | def test_as_node_receives_as_parameter(self): 300 | parser = template.Parser([]) 301 | token = template.Token(template.TOKEN_BLOCK, u'tag_name as varname') 302 | 303 | MyEasyAsNode = type('MyEasyAsNode', (EasyAsNode,), { 304 | 'render_context': lambda self, context, **kwargs: 'value' 305 | }) 306 | 307 | node = MyEasyAsNode.parse(parser, token) 308 | context = Context() 309 | 310 | self.assertEqual('', node.render(context)) 311 | self.assertEqual('value', context['varname']) 312 | 313 | def test_as_node_can_be_used_without_as_parameter(self): 314 | parser = template.Parser([]) 315 | token = template.Token(template.TOKEN_BLOCK, u'tag_name "value"') 316 | 317 | MyEasyAsNode = type('MyEasyAsNode', (EasyAsNode,), { 318 | 'render_context': lambda self, context, arg1, **kwargs: arg1 319 | }) 320 | 321 | node = MyEasyAsNode.parse(parser, token) 322 | context = Context() 323 | 324 | self.assertEqual('value', node.render(context)) 325 | 326 | -------------------------------------------------------------------------------- /src/easytags/tests/test_parser.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | ''' 4 | Created on 20/02/2011 5 | 6 | @author: vbmendes 7 | ''' 8 | 9 | from django import template 10 | from django.test import TestCase 11 | 12 | from easytags.node import EasyNode, EasyAsNode 13 | 14 | 15 | class ParserTests(TestCase): 16 | 17 | def test_environment(self): 18 | """ 19 | Just make sure everything is set up correctly. 20 | """ 21 | self.assertTrue(True) 22 | 23 | def test_parse_tag_with_args(self): 24 | """ 25 | Tests if the parser recognizes one tag and parses its args 26 | """ 27 | parser = template.Parser([]) 28 | token = template.Token(template.TOKEN_BLOCK, 'tag_name "arg1" "arg2"') 29 | args_kwargs = EasyNode.parse_to_args_kwargs(parser, token) 30 | args_kwargs_str = {'args': tuple([x.token for x in args_kwargs['args']]), 31 | 'kwargs': dict((key, value.token) for key, value in args_kwargs['kwargs'].iteritems())} 32 | self.assertEquals( 33 | {'args': ('"arg1"', '"arg2"'), 'kwargs': {}}, 34 | args_kwargs_str 35 | ) 36 | 37 | def test_parse_tag_with_kwargs(self): 38 | """ 39 | Tests if the parser recognizes one tag and parses its kwargs 40 | """ 41 | parser = template.Parser([]) 42 | token = template.Token(template.TOKEN_BLOCK, 'tag_name kwarg1="1" kwarg2="2"') 43 | args_kwargs = EasyNode.parse_to_args_kwargs(parser, token) 44 | args_kwargs_str = {'args': tuple([x.token for x in args_kwargs['args']]), 45 | 'kwargs': dict((key, value.token) for key, value in args_kwargs['kwargs'].iteritems())} 46 | self.assertEquals( 47 | {'args': (), 'kwargs': {'kwarg1': '"1"', 'kwarg2': '"2"'}}, 48 | args_kwargs_str 49 | ) 50 | 51 | def test_parse_tag_with_args_and_kwargs(self): 52 | """ 53 | Tests if the parser recognizes one tag and parses its args and kwargs 54 | """ 55 | parser = template.Parser([]) 56 | token = template.Token(template.TOKEN_BLOCK, 'tag_name "arg1" kwarg1="1"') 57 | args_kwargs = EasyNode.parse_to_args_kwargs(parser, token) 58 | args_kwargs_str = {'args': tuple([x.token for x in args_kwargs['args']]), 59 | 'kwargs': dict((key, value.token) for key, value in args_kwargs['kwargs'].iteritems())} 60 | self.assertEquals( 61 | {'args': ('"arg1"',), 'kwargs': {'kwarg1': '"1"'}}, 62 | args_kwargs_str 63 | ) 64 | 65 | def test_parse_tag_with_variable_arg(self): 66 | parser = template.Parser([]) 67 | token = template.Token(template.TOKEN_BLOCK, 'tag_name argvariable') 68 | args_kwargs = EasyNode.parse_to_args_kwargs(parser, token) 69 | args_kwargs_str = {'args': tuple([x.token for x in args_kwargs['args']]), 70 | 'kwargs': dict((key, value.token) for key, value in args_kwargs['kwargs'].iteritems())} 71 | self.assertEquals( 72 | {'args': ('argvariable',), 'kwargs': {}}, 73 | args_kwargs_str 74 | ) 75 | 76 | def test_parse_tag_with_equals_in_arg_value(self): 77 | parser = template.Parser([]) 78 | token = template.Token(template.TOKEN_BLOCK, 'tag_name "a=1"') 79 | args_kwargs = EasyNode.parse_to_args_kwargs(parser, token) 80 | args_kwargs_str = {'args': tuple([x.token for x in args_kwargs['args']]), 81 | 'kwargs': dict((key, value.token) for key, value in args_kwargs['kwargs'].iteritems())} 82 | self.assertEquals( 83 | {'args': ('"a=1"',), 'kwargs': {}}, 84 | args_kwargs_str 85 | ) 86 | 87 | def test_parse_tag_with_equals_in_kwarg_value(self): 88 | parser = template.Parser([]) 89 | token = template.Token(template.TOKEN_BLOCK, 'tag_name kwarg1="a=1"') 90 | args_kwargs = EasyNode.parse_to_args_kwargs(parser, token) 91 | args_kwargs_str = {'args': tuple([x.token for x in args_kwargs['args']]), 92 | 'kwargs': dict((key, value.token) for key, value in args_kwargs['kwargs'].iteritems())} 93 | self.assertEquals( 94 | {'args': (), 'kwargs': {'kwarg1': '"a=1"'}}, 95 | args_kwargs_str 96 | ) 97 | 98 | def test_parse_tag_special_symbol_in_arg_value(self): 99 | parser = template.Parser([]) 100 | token = template.Token(template.TOKEN_BLOCK, u'tag_name "será?"') 101 | args_kwargs = EasyNode.parse_to_args_kwargs(parser, token) 102 | args_kwargs_str = {'args': tuple([x.token for x in args_kwargs['args']]), 103 | 'kwargs': dict((key, value.token) for key, value in args_kwargs['kwargs'].iteritems())} 104 | self.assertEquals( 105 | {'args': (u'"será?"',), 'kwargs': {}}, 106 | args_kwargs_str 107 | ) 108 | 109 | def test_parse_tag_special_symbol_in_kwarg_value(self): 110 | parser = template.Parser([]) 111 | token = template.Token(template.TOKEN_BLOCK, u'tag_name kwarg1="será?"') 112 | args_kwargs = EasyNode.parse_to_args_kwargs(parser, token) 113 | args_kwargs_str = {'args': tuple([x.token for x in args_kwargs['args']]), 114 | 'kwargs': dict((key, value.token) for key, value in args_kwargs['kwargs'].iteritems())} 115 | self.assertEquals( 116 | {'args': (), 'kwargs': {'kwarg1': u'"será?"'}}, 117 | args_kwargs_str 118 | ) 119 | 120 | def test_parse_tag_with_args_after_kwargs_raises_exception(self): 121 | parser = template.Parser([]) 122 | token = template.Token(template.TOKEN_BLOCK, u'tag_name kwarg1="será?" my_arg') 123 | self.assertRaises(template.TemplateSyntaxError, 124 | EasyNode.parse_to_args_kwargs, parser, token 125 | ) 126 | 127 | def test_parse_as_tag_with_args(self): 128 | """ 129 | Tests if the parser recognizes one tag and parses its args even when using EasyAsNode 130 | """ 131 | parser = template.Parser([]) 132 | token = template.Token(template.TOKEN_BLOCK, 'tag_name "arg1" "arg2"') 133 | args_kwargs = EasyAsNode.parse_to_args_kwargs(parser, token) 134 | args_kwargs_str = {'args': tuple([x.token for x in args_kwargs['args']]), 135 | 'kwargs': dict((key, value.token) for key, value in args_kwargs['kwargs'].iteritems()), 136 | 'varname': args_kwargs['varname']} 137 | self.assertEquals( 138 | {'args': ('"arg1"', '"arg2"'), 'kwargs': {}, 'varname': None}, 139 | args_kwargs_str 140 | ) 141 | 142 | def test_parse_as_tag_with_args_and_as_parameter(self): 143 | """ 144 | Tests if the parser recognizes one tag and parses its args and as parameter 145 | """ 146 | parser = template.Parser([]) 147 | token = template.Token(template.TOKEN_BLOCK, 'tag_name "arg1" "arg2" as varname') 148 | args_kwargs = EasyAsNode.parse_to_args_kwargs(parser, token) 149 | args_kwargs_str = {'args': tuple([x.token for x in args_kwargs['args']]), 150 | 'kwargs': dict((key, value.token) for key, value in args_kwargs['kwargs'].iteritems()), 151 | 'varname': args_kwargs['varname']} 152 | self.assertEquals( 153 | {'args': ('"arg1"', '"arg2"'), 'kwargs': {}, 'varname': 'varname'}, 154 | args_kwargs_str 155 | ) 156 | -------------------------------------------------------------------------------- /src/easytags/testsettings.py: -------------------------------------------------------------------------------- 1 | DATABASE_ENGINE = 'sqlite3' 2 | DATABASE_NAME = '/tmp/easytags.db' 3 | INSTALLED_APPS = ('easytags',) 4 | ROOT_URLCONF = ('easytags.urls',) 5 | --------------------------------------------------------------------------------