├── .coveragerc ├── .editorconfig ├── .env ├── .gitignore ├── .travis.yml ├── AUTHORS ├── CHANGELOG.md ├── LICENSE ├── MANIFEST ├── README.md ├── logo.png ├── requirements ├── common.txt ├── development.txt └── release.txt ├── setup.py ├── static ├── config.rb ├── images │ ├── calendar_icon.gif │ ├── help.png │ ├── hide.gif │ ├── show.gif │ └── throbber.gif ├── javascript │ └── thedom.js ├── sass │ ├── WebElementStructure.sass │ ├── WebElementStructureWithButtons.sass │ ├── WebElements.sass │ ├── _CodeDocumentation.scss │ ├── _Fonts.sass │ ├── _Utils.sass │ └── _sassybuttons.scss └── stylesheets │ ├── .sass-cache │ └── 49b65b430aee62cf132001fd9def67cd0781a8ac │ │ ├── WebElements.sassc │ │ └── _CodeDocumentation.scssc │ ├── WebElementStructure.css │ ├── WebElementStructureWithButtons.css │ ├── WebElements.css │ ├── _CodeDocumentation.scss │ ├── ie.css │ ├── print.css │ └── screen.css ├── tests ├── benchmark_thedom.py ├── test_base.py ├── test_buttons.py ├── test_charts.py ├── test_connectable.py ├── test_container.py ├── test_dataviews.py ├── test_dict_utils.py ├── test_display.py ├── test_dom.py ├── test_factory.py ├── test_hidden_inputs.py ├── test_inputs.py ├── test_iterator_utils.py ├── test_layout.py ├── test_navigation.py ├── test_parser.py ├── test_position_controller.py ├── test_printing.py ├── test_resources.py ├── test_social.py ├── test_string_utils.py ├── test_template.shpaml ├── test_template.xml ├── test_ui_template.py └── tests_benchmark.py ├── thedom ├── __init__.py ├── all.py ├── base.py ├── buttons.py ├── charts.py ├── clienst_side.py ├── code_documentation.py ├── common_javascript.js ├── compile.py ├── connectable.py ├── containers.py ├── controllers │ ├── __init__.py │ ├── app_engine.py │ ├── dynamic_form.py │ ├── http.py │ ├── page_controls.py │ └── request_handler.py ├── data_views.py ├── dict_utils.py ├── display.py ├── document.py ├── dom.py ├── factory.py ├── hidden_inputs.py ├── inputs.py ├── iterator_utils.py ├── json_parser.py ├── layout.py ├── list_utils.py ├── method_utils.py ├── navigation.py ├── parser.py ├── position_controller.py ├── printing.py ├── resources.py ├── shpaml.py ├── social.py ├── string_utils.py ├── types.py ├── ui_template.py └── validators.py └── tox.ini /.coveragerc: -------------------------------------------------------------------------------- 1 | [report] 2 | include = hug/*.py 3 | exclude_lines = def terminal 4 | def serve 5 | 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.py] 4 | max_line_length = 120 5 | indent_style = space 6 | indent_size = 4 7 | ignore_frosted_errors = E103 8 | skip = runtests.py,build 9 | balanced_wrapping = true 10 | not_skip = __init__.py 11 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | OPEN_PROJECT_NAME="thedom" 3 | 4 | if [ "$PROJECT_NAME" = "$OPEN_PROJECT_NAME" ]; then 5 | return 6 | fi 7 | 8 | if [ ! -f ".env" ]; then 9 | return 10 | fi 11 | 12 | export PROJECT_NAME=$OPEN_PROJECT_NAME 13 | export PROJECT_DIR="$PWD" 14 | 15 | if [ ! -f "pyvenv.cfg" ]; then 16 | if ! hash pyvenv 2>/dev/null; then 17 | alias pyvenv="pyvenv-3.4"; 18 | fi 19 | 20 | echo "Making venv for $PROJECT_NAME" 21 | pyvenv . 22 | . bin/activate 23 | pip install -r requirements/development.txt 24 | fi 25 | 26 | . bin/activate 27 | 28 | # Let's make sure this is a hubflow enabled repo 29 | yes | git hf init >/dev/null 2>/dev/null 30 | 31 | # Quick directory switching 32 | alias root="cd $PROJECT_DIR" 33 | alias project="root; cd $PROJECT_NAME" 34 | alias tests="root; cd tests" 35 | alias test="_test" 36 | 37 | 38 | function _start { 39 | export LAST_DIRECTORY="$PWD" 40 | root 41 | } 42 | 43 | 44 | function _end { 45 | cd $LAST_DIRECTORY 46 | unset LAST_DIRECTORY 47 | } 48 | 49 | 50 | function open { 51 | _start; $CODE_EDITOR thedom/*.py setup.py tests/*.py README.md tox.ini .gitignore CHANGELOG.md setup.cfg .editorconfig .env .coveragerc .travis.yml; _end 52 | } 53 | 54 | 55 | function clean { 56 | _start; isort thedom/*.py setup.py; _end 57 | } 58 | 59 | 60 | function _test { 61 | _start; tox; _end 62 | } 63 | 64 | 65 | function coverage { 66 | _start; py.test --cov-report html --cov thedom/ tests 67 | $BROWSER htmlcov/index.html; _end 68 | } 69 | 70 | 71 | function load { 72 | _start; python setup.py install; _end 73 | } 74 | 75 | 76 | function unload { 77 | _start; pip uninstall thedom; _end 78 | } 79 | 80 | 81 | function install { 82 | _start; sudo python setup.py install; _end 83 | } 84 | 85 | 86 | function distribute { 87 | _start; python setup.py sdist upload 88 | python setup.py bdist_wheel upload; _end 89 | } 90 | 91 | 92 | function leave { 93 | export PROJECT_NAME="" 94 | export PROJECT_DIR="" 95 | 96 | unalias root 97 | unalias project 98 | unalias test 99 | 100 | unset -f _start 101 | unset -f _end 102 | 103 | 104 | unset -f open 105 | unset -f clean 106 | unset -f _test 107 | unset -f coverage 108 | unset -f load 109 | unset -f unload 110 | unset -f install 111 | unset -f distribute 112 | 113 | unset -f leave 114 | } 115 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | .DS_Store 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | build 10 | eggs 11 | parts 12 | var 13 | sdist 14 | develop-eggs 15 | .installed.cfg 16 | lib 17 | lib64 18 | MANIFEST 19 | 20 | # Installer logs 21 | pip-log.txt 22 | npm-debug.log 23 | pip-selfcheck.json 24 | 25 | # Unit test / coverage reports 26 | .coverage 27 | .tox 28 | nosetests.xml 29 | htmlcov 30 | .cache 31 | 32 | # Translations 33 | *.mo 34 | 35 | # Mr Developer 36 | .mr.developer.cfg 37 | .project 38 | .pydevproject 39 | 40 | # SQLite 41 | test_exp_framework 42 | 43 | # npm 44 | node_modules/ 45 | 46 | # dolphin 47 | .directory 48 | libpeerconnection.log 49 | 50 | # setuptools 51 | dist 52 | 53 | # IDE Files 54 | atlassian-ide-plugin.xml 55 | .idea/ 56 | *.swp 57 | *.kate-swp 58 | .ropeproject/ 59 | 60 | # Python3 Venv Files 61 | bin/ 62 | include/ 63 | lib/ 64 | lib64 65 | pyvenv.cfg 66 | share/ 67 | 68 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3.4 3 | install: 4 | - pip install tox 5 | - pip install python-coveralls 6 | script: tox 7 | after_success: 8 | - coveralls 9 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Contributors 2 | ------------ 3 | 4 | * Timothy Crosley 5 | * Terrence Brannon 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | ### 0.0.1 5 | - Initial release of `thedom` 6 | -------------------------------------------------------------------------------- /MANIFEST: -------------------------------------------------------------------------------- 1 | # file GENERATED by distutils, do NOT edit 2 | setup.py 3 | WebElements/All.py 4 | WebElements/Base.py 5 | WebElements/Buttons.py 6 | WebElements/Charts.py 7 | WebElements/ClientSide.py 8 | WebElements/CodeDocumentation.py 9 | WebElements/Connectable.py 10 | WebElements/Containers.py 11 | WebElements/DOM.py 12 | WebElements/DataViews.py 13 | WebElements/DictUtils.py 14 | WebElements/Display.py 15 | WebElements/Document.py 16 | WebElements/Factory.py 17 | WebElements/HiddenInputs.py 18 | WebElements/Inputs.py 19 | WebElements/IteratorUtils.py 20 | WebElements/JsonParser.py 21 | WebElements/Layout.py 22 | WebElements/MethodUtils.py 23 | WebElements/MultiplePythonSupport.py 24 | WebElements/Navigation.py 25 | WebElements/Parser.py 26 | WebElements/PositionController.py 27 | WebElements/Printing.py 28 | WebElements/Resources.py 29 | WebElements/Social.py 30 | WebElements/StringUtils.py 31 | WebElements/Types.py 32 | WebElements/UITemplate.py 33 | WebElements/Validators.py 34 | WebElements/__init__.py 35 | WebElements/shpaml.py 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![thedom](https://raw.github.com/timothycrosley/thedom/develop/logo.png) 2 | ===== 3 | 4 | [![PyPI version](https://badge.fury.io/py/thedom.png)](http://badge.fury.io/py/thedom) 5 | [![PyPi downloads](https://pypip.in/d/thedom/badge.png)](https://crate.io/packages/thedom/) 6 | [![Build Status](https://travis-ci.org/timothycrosley/thedom.png?branch=master)](https://travis-ci.org/timothycrosley/thedom) 7 | [![License](https://pypip.in/license/thedom/badge.png)](https://pypi.python.org/pypi/thedom/) 8 | [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/timothycrosley/thedom/trend.png)](https://bitdeli.com/free "Bitdeli Badge") 9 | 10 | thedom is a collection of python objects that enable developers to generate and interact with web apps server side. 11 | It encourages object oriented website development, and code reuse by seperating each DOM element into its own object, 12 | and then allowing inheritance and child elements to come together to form new elements not defined in the standard DOM. 13 | 14 | write this: 15 | 16 | from thedom import layout, document, buttons 17 | 18 | page = document.Document() 19 | layout = page.addChildElement(layout.Center()).addChildElement(layout.Horizontal()) 20 | layout += buttons.Button(text="Use thedom.", **{'class':'MainAction'}) 21 | layout += buttons.Button(text="Enjoy writing less code.", **{'class':'DeleteAction'}) 22 | layout += buttons.Button(text="100% Python.") 23 | 24 | print page.toHTML(formatted=True) 25 | 26 | get this: 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |
38 |
39 |
40 |
41 | 42 | 43 | 44 |
45 |
46 |
47 |
48 | 49 | 50 | 51 | Installing thedom 52 | =================== 53 | 54 | Installing thedom is as simple as: 55 | 56 | pip install thedom 57 | 58 | or if you prefer 59 | 60 | easy_install thedom 61 | 62 | -------------------------------------------- 63 | 64 | Thanks and I hope you find thedom useful! 65 | 66 | ~Timothy Crosley 67 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timothycrosley/thedom/73833110a298456a1f24ab8e4f5cec09a0655c2d/logo.png -------------------------------------------------------------------------------- /requirements/common.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timothycrosley/thedom/73833110a298456a1f24ab8e4f5cec09a0655c2d/requirements/common.txt -------------------------------------------------------------------------------- /requirements/development.txt: -------------------------------------------------------------------------------- 1 | -r common.txt 2 | pytest==2.6.1 3 | pytest-cov==1.8.1 4 | tox==1.7.2 5 | isort==4.2.2 6 | Cython==0.22.1 7 | ipython==3.2.1 8 | wheel==0.24.0 9 | python-coveralls==2.5.0 10 | -------------------------------------------------------------------------------- /requirements/release.txt: -------------------------------------------------------------------------------- 1 | -r common.txt 2 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import subprocess 4 | import sys 5 | 6 | try: 7 | from setuptools import setup 8 | from setuptools.command.test import test as TestCommand 9 | 10 | class PyTest(TestCommand): 11 | extra_kwargs = {'tests_require': ['pytest', 'mock']} 12 | 13 | def finalize_options(self): 14 | TestCommand.finalize_options(self) 15 | self.test_args = [] 16 | self.test_suite = True 17 | 18 | def run_tests(self): 19 | import pytest 20 | sys.exit(pytest.main(self.test_args)) 21 | 22 | except ImportError: 23 | from distutils.core import setup, Command 24 | 25 | class PyTest(Command): 26 | extra_kwargs = {} 27 | user_options = [] 28 | 29 | def initialize_options(self): 30 | pass 31 | 32 | def finalize_options(self): 33 | pass 34 | 35 | def run(self): 36 | raise SystemExit(subprocess.call([sys.executable, 'runtests.py'])) 37 | 38 | try: 39 | import pypandoc 40 | readme = pypandoc.convert('README.md', 'rst') 41 | except (IOError, ImportError, OSError, RuntimeError): 42 | readme = '' 43 | 44 | setup(name='thedom', 45 | version='0.0.1', 46 | long_description=readme, 47 | description='thedom is a collection of python objects that enable you to represent and interact with the DOM server side before generating HTML, CSS, and JavaScript for the client.', 48 | author_email='timothy.crosley@gmail.com', 49 | url='https://github.com/timothycrosley/thedom', 50 | download_url='https://github.com/timothycrosley/thedom/archive/1.0.0-beta.2.tar.gz', 51 | license="MIT", 52 | packages=['thedom'], 53 | requires=['pies'], 54 | install_requires=['pies>=2.5.5'], 55 | cmdclass={'test': PyTest}, 56 | keywords='Web, Python, Python2, Python3, Dom, HTML, Library, Parser', 57 | classifiers=['Development Status :: 6 - Mature', 58 | 'Intended Audience :: Developers', 59 | 'Natural Language :: English', 60 | 'License :: OSI Approved :: MIT License', 61 | 'Programming Language :: Python', 62 | 'Programming Language :: Python :: 2', 63 | 'Programming Language :: Python :: 2.6', 64 | 'Programming Language :: Python :: 2.7', 65 | 'Programming Language :: Python :: 3', 66 | 'Programming Language :: Python :: 3.0', 67 | 'Programming Language :: Python :: 3.1', 68 | 'Programming Language :: Python :: 3.2', 69 | 'Programming Language :: Python :: 3.3', 70 | 'Topic :: Software Development :: Libraries'], 71 | **PyTest.extra_kwargs) 72 | -------------------------------------------------------------------------------- /static/config.rb: -------------------------------------------------------------------------------- 1 | # Require any additional compass plugins here. 2 | require 'sassy-buttons' 3 | 4 | # Set this to the root of your project when deployed: 5 | http_path = "/" 6 | css_dir = "stylesheets" 7 | sass_dir = "sass" 8 | images_dir = "images" 9 | javascripts_dir = "javascript" 10 | 11 | # You can select your preferred output style here (can be overridden via the command line): 12 | # output_style = :expanded or :nested or :compact or :compressed 13 | 14 | # To enable relative paths to assets via compass helper functions. Uncomment: 15 | # relative_assets = true 16 | 17 | # To disable debugging comments that display the original location of your selectors. Uncomment: 18 | # line_comments = false 19 | 20 | 21 | # If you prefer the indented syntax, you might want to regenerate this 22 | # project again passing --syntax sass, or you can uncomment this: 23 | # preferred_syntax = :sass 24 | # and then run: 25 | # sass-convert -R --from scss --to sass sass scss && rm -rf sass && mv scss sass 26 | -------------------------------------------------------------------------------- /static/images/calendar_icon.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timothycrosley/thedom/73833110a298456a1f24ab8e4f5cec09a0655c2d/static/images/calendar_icon.gif -------------------------------------------------------------------------------- /static/images/help.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timothycrosley/thedom/73833110a298456a1f24ab8e4f5cec09a0655c2d/static/images/help.png -------------------------------------------------------------------------------- /static/images/hide.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timothycrosley/thedom/73833110a298456a1f24ab8e4f5cec09a0655c2d/static/images/hide.gif -------------------------------------------------------------------------------- /static/images/show.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timothycrosley/thedom/73833110a298456a1f24ab8e4f5cec09a0655c2d/static/images/show.gif -------------------------------------------------------------------------------- /static/images/throbber.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timothycrosley/thedom/73833110a298456a1f24ab8e4f5cec09a0655c2d/static/images/throbber.gif -------------------------------------------------------------------------------- /static/sass/WebElementStructureWithButtons.sass: -------------------------------------------------------------------------------- 1 | @import "WebElementStructure.sass" 2 | @import "sassybuttons" 3 | 4 | @mixin basicButton 5 | +sassy-button("simple", 10px, 16px, #E4E4E4, false, black) 6 | 7 | @mixin mainActionButton 8 | +sassy-button("simple", 10px, 16px, #669F5F) 9 | 10 | @mixin deleteActionButton 11 | +sassy-button("simple", 10px, 16px, #C30000) 12 | 13 | @mixin positiveAction 14 | +sassy-button("simple", 10px, 16px, #3e8dc9) 15 | 16 | @mixin toggleButtonUnclicked 17 | +sassy-button("shiny", 10px, 16px, #7C7C7C) 18 | 19 | @mixin toggleButtonClicked 20 | +sassy-button-style("inset", #474747) 21 | 22 | @mixin disabledButton 23 | +sassy-button-style("inset", gray) 24 | +indent 25 | 26 | 27 | input[type=button], input[type=submit], .WButton, button 28 | +basicButton 29 | &.MainAction 30 | +mainActionButton 31 | &.PositiveAction 32 | +positiveAction 33 | &.DeleteAction 34 | +deleteActionButton 35 | &.WToggleButton 36 | +toggleButtonUnclicked 37 | &.Pushed 38 | +toggleButtonClicked 39 | 40 | &[disabled] 41 | +disabledButton 42 | &:active 43 | @include indent 44 | 45 | .WButtonGroup 46 | input[type=button], input[type=submit] 47 | border-radius: 0px 0px 0px 0px 48 | .WFirst 49 | input[type=button], input[type=submit] 50 | border-radius: 8px 0px 0px 8px 51 | .WLast 52 | input[type=button], input[type=submit] 53 | border-radius: 0px 8px 8px 0px 54 | -------------------------------------------------------------------------------- /static/sass/WebElements.sass: -------------------------------------------------------------------------------- 1 | @import "compass" 2 | @import "compass/reset/utilities" 3 | @include global-reset 4 | @import "WebElementStructureWithButtons" 5 | @import "Fonts" 6 | @import "compass/css3/border-radius" 7 | 8 | * 9 | @include box-sizing(border-box) 10 | 11 | img 12 | vertical-align: middle 13 | 14 | iframe 15 | border: 0px none transparent !important 16 | 17 | a 18 | font-weight: bold 19 | text-decoration: none 20 | color: #2574B0 21 | &:hover 22 | text-decoration: underline 23 | &:visited 24 | color: #6847B4 25 | 26 | label 27 | cursor: inherit 28 | 29 | ul 30 | list-style: disc 31 | ol 32 | list-style: decimal 33 | 34 | @mixin selectedRow 35 | background: #548BB5 36 | color: #ffffff 37 | & a 38 | color: #ffffff 39 | 40 | td 41 | color: #000 42 | 43 | table.table-background td 44 | border: 1px solid #EEF0F4 45 | 46 | th 47 | background-color: #c4d0e4 48 | border-bottom-style: solid 49 | border-bottom-width: thin 50 | border-bottom-color: #000000 51 | font-family: Arial, Helvetica, sans-serif 52 | text-decoration: none 53 | 54 | .yellow 55 | background-color: #DF8C42 56 | 57 | .rowlight 58 | background-color: #f0f0f0 59 | border: 1px solid #000000 60 | 61 | .rowdark 62 | background-color: #DADADA 63 | border: 1px solid #000000 64 | 65 | 66 | table.GlobalTable 67 | border-spacing: 0 68 | tr 69 | border: none 70 | height: 25px 71 | color: #000000 72 | background: #F2FAFF /* This background is for IE */ 73 | a 74 | text-decoration: underline 75 | color: #0963BD 76 | a:hover 77 | color: #0051A3 78 | &.taskColorHigh 79 | background: #f77 80 | & td 81 | border-bottom: 1px solid #EEE 82 | &.taskColorMedium 83 | background: #ff9 84 | &.taskColorLow 85 | background: #9f9 86 | &.warning 87 | background: #fd7 88 | &.error 89 | background: #fc797b 90 | tr:hover 91 | background: #FAFACD /* This hover is for IE */ 92 | &.selected 93 | background: darken(#548BB5, 20%) 94 | tr:nth-child(even) 95 | background: #FFFFFF 96 | &.taskColorHigh 97 | background: #f77 98 | &.taskColorMedium 99 | background: #ff9 100 | &.taskColorLow 101 | background: #9f9 102 | &.warning 103 | background: #fd7 104 | &.error 105 | background: #fc797b 106 | &.selected 107 | @include selectedRow 108 | tr:nth-child(odd) 109 | background: #DEDEDE 110 | &.taskColorHigh 111 | background: #f77 112 | &.taskColorMedium 113 | background: #ff9 114 | &.taskColorLow 115 | background: #9f9 116 | &.warning 117 | background: #fd7 118 | &.error 119 | background: #fc797b 120 | &.selected 121 | @include selectedRow 122 | tr:nth-child(even):hover 123 | background: #FAFACD 124 | &.selected 125 | background: darken(#548BB5, 20%) 126 | tr:nth-child(odd):hover 127 | background: #FAFACD 128 | &.selected 129 | background: darken(#548BB5, 20%) 130 | th 131 | text-align: left 132 | border-right: 1px solid lighten(#3089E7, 10%) 133 | border-bottom: none 134 | border-left: none 135 | border-top: none 136 | padding-left: 4px 137 | height: 30px 138 | background: #499bea /* Old browsers */ 139 | background: -moz-linear-gradient(top, #499bea 0%, #207ce5 100%) /* FF3.6+ */ 140 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#499bea), color-stop(100%,#207ce5)) /* Chrome,Safari4+ */ 141 | background: -webkit-linear-gradient(top, #499bea 0%,#207ce5 100%) /* Chrome10+,Safari5.1+ */ 142 | background: -o-linear-gradient(top, #499bea 0%,#207ce5 100%) /* Opera 11.10+ */ 143 | background: -ms-linear-gradient(top, #499bea 0%,#207ce5 100%) /* IE10+ */ 144 | background: linear-gradient(top, #499bea 0%,#207ce5 100%) /* W3C */ 145 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#499bea', endColorstr='#207ce5',GradientType=0 ) /* IE6-9 */ 146 | color: #FFFFFF 147 | white-space: nowrap 148 | a 149 | text-decoration: underline 150 | color: white 151 | a:hover 152 | color: darken(white, 20%) 153 | a:active 154 | color: darken(white, 40%) 155 | td 156 | border-right: 1px solid #EEEEEE 157 | border-left: none 158 | border-bottom: none 159 | border-top: none 160 | padding-left: 3px 161 | &.TableViewSeparator 162 | background: #808080 163 | color: #FFFFFF 164 | 165 | .selected td 166 | @include selectedRow 167 | 168 | .rowdark 169 | background: #DEDEDE\9 170 | &.selected 171 | @include selectedRow 172 | 173 | .rowlight 174 | background: #FFFFFF\9 175 | &.selected 176 | @include selectedRow 177 | 178 | -------------------------------------------------------------------------------- /static/sass/_CodeDocumentation.scss: -------------------------------------------------------------------------------- 1 | .highlight .hll { background-color: #49483e } 2 | .highlight { background: #272822; color: #f8f8f2 } 3 | .highlight .c { color: #75715e } /* Comment */ 4 | .highlight .err { color: #960050; background-color: #1e0010 } /* Error */ 5 | .highlight .k { color: #66d9ef } /* Keyword */ 6 | .highlight .l { color: #ae81ff } /* Literal */ 7 | .highlight .n { color: #f8f8f2 } /* Name */ 8 | .highlight .o { color: #f92672 } /* Operator */ 9 | .highlight .p { color: #f8f8f2 } /* Punctuation */ 10 | .highlight .cm { color: #75715e } /* Comment.Multiline */ 11 | .highlight .cp { color: #75715e } /* Comment.Preproc */ 12 | .highlight .c1 { color: #75715e } /* Comment.Single */ 13 | .highlight .cs { color: #75715e } /* Comment.Special */ 14 | .highlight .ge { font-style: italic } /* Generic.Emph */ 15 | .highlight .gs { font-weight: bold } /* Generic.Strong */ 16 | .highlight .kc { color: #66d9ef } /* Keyword.Constant */ 17 | .highlight .kd { color: #66d9ef } /* Keyword.Declaration */ 18 | .highlight .kn { color: #f92672 } /* Keyword.Namespace */ 19 | .highlight .kp { color: #66d9ef } /* Keyword.Pseudo */ 20 | .highlight .kr { color: #66d9ef } /* Keyword.Reserved */ 21 | .highlight .kt { color: #66d9ef } /* Keyword.Type */ 22 | .highlight .ld { color: #e6db74 } /* Literal.Date */ 23 | .highlight .m { color: #ae81ff } /* Literal.Number */ 24 | .highlight .s { color: #e6db74 } /* Literal.String */ 25 | .highlight .na { color: #a6e22e } /* Name.Attribute */ 26 | .highlight .nb { color: #f8f8f2 } /* Name.Builtin */ 27 | .highlight .nc { color: #a6e22e } /* Name.Class */ 28 | .highlight .no { color: #66d9ef } /* Name.Constant */ 29 | .highlight .nd { color: #a6e22e } /* Name.Decorator */ 30 | .highlight .ni { color: #f8f8f2 } /* Name.Entity */ 31 | .highlight .ne { color: #a6e22e } /* Name.Exception */ 32 | .highlight .nf { color: #a6e22e } /* Name.Function */ 33 | .highlight .nl { color: #f8f8f2 } /* Name.Label */ 34 | .highlight .nn { color: #f8f8f2 } /* Name.Namespace */ 35 | .highlight .nx { color: #a6e22e } /* Name.Other */ 36 | .highlight .py { color: #f8f8f2 } /* Name.Property */ 37 | .highlight .nt { color: #f92672 } /* Name.Tag */ 38 | .highlight .nv { color: #f8f8f2 } /* Name.Variable */ 39 | .highlight .ow { color: #f92672 } /* Operator.Word */ 40 | .highlight .w { color: #f8f8f2 } /* Text.Whitespace */ 41 | .highlight .mf { color: #ae81ff } /* Literal.Number.Float */ 42 | .highlight .mh { color: #ae81ff } /* Literal.Number.Hex */ 43 | .highlight .mi { color: #ae81ff } /* Literal.Number.Integer */ 44 | .highlight .mo { color: #ae81ff } /* Literal.Number.Oct */ 45 | .highlight .sb { color: #e6db74 } /* Literal.String.Backtick */ 46 | .highlight .sc { color: #e6db74 } /* Literal.String.Char */ 47 | .highlight .sd { color: #e6db74 } /* Literal.String.Doc */ 48 | .highlight .s2 { color: #e6db74 } /* Literal.String.Double */ 49 | .highlight .se { color: #ae81ff } /* Literal.String.Escape */ 50 | .highlight .sh { color: #e6db74 } /* Literal.String.Heredoc */ 51 | .highlight .si { color: #e6db74 } /* Literal.String.Interpol */ 52 | .highlight .sx { color: #e6db74 } /* Literal.String.Other */ 53 | .highlight .sr { color: #e6db74 } /* Literal.String.Regex */ 54 | .highlight .s1 { color: #e6db74 } /* Literal.String.Single */ 55 | .highlight .ss { color: #e6db74 } /* Literal.String.Symbol */ 56 | .highlight .bp { color: #f8f8f2 } /* Name.Builtin.Pseudo */ 57 | .highlight .vc { color: #f8f8f2 } /* Name.Variable.Class */ 58 | .highlight .vg { color: #f8f8f2 } /* Name.Variable.Global */ 59 | .highlight .vi { color: #f8f8f2 } /* Name.Variable.Instance */ 60 | .highlight .il { color: #ae81ff } /* Literal.Number.Integer.Long */ 61 | -------------------------------------------------------------------------------- /static/sass/_Fonts.sass: -------------------------------------------------------------------------------- 1 | // We should only have 2 font-families throught the ADC site, one for content and another for headings 2 | 3 | $family: helvetica, arial, sans-serif 4 | 5 | body 6 | font-family: $family 7 | font-size: 100% 8 | line-height: 1.25 9 | 10 | small 11 | font-size: .8em 12 | 13 | p 14 | line-height: 1.5 15 | text-indent: 1em 16 | 17 | em 18 | font-style: italic 19 | 20 | strong 21 | font-weight: bold 22 | 23 | $baseHeaderSize: 3.25 24 | @for $level from 1 through 6 25 | h#{$level} 26 | $headerSize: ($baseHeaderSize - (.25 * $level)) 27 | font-size: $headerSize * 1em 28 | @if $level == 1 29 | font-weight: bold 30 | @if $level == 6 31 | text-decoration: underline 32 | 33 | -------------------------------------------------------------------------------- /static/sass/_Utils.sass: -------------------------------------------------------------------------------- 1 | @mixin gradient($start, $end, $from:top) 2 | @if $from == top 3 | +filter-gradient($start, $end) 4 | @else 5 | +filter-gradient($start, $end, horizontal) 6 | background: $start 7 | +background(linear-gradient($from, $start, $end)) 8 | 9 | 10 | @mixin radial($center, $background, $size) 11 | background: $background 12 | position: relative \9 13 | .IEHack 14 | position: absolute \9 15 | top: 0 \9 16 | left: 0 \9 17 | background: $center \9 18 | width: $size \9 19 | height: $size \9 20 | -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(opacity=60, finishopacity=0, style=2)" 21 | filter: unquote("progid:DXImageTransform.Microsoft.Alpha(opacity=60, finishopacity=0, style=2)") 22 | 23 | +background(radial-gradient($center, $background $size)) 24 | 25 | @mixin shadow() 26 | +single-box-shadow() 27 | -ms-filter: "progid:DXImageTransform.Microsoft.Shadow(Strength=4, Direction=135, Color='#000000')" 28 | filter: unquote("progid:DXImageTransform.Microsoft.Shadow(Strength=4, Direction=135, Color='#000000')") 29 | -------------------------------------------------------------------------------- /static/sass/_sassybuttons.scss: -------------------------------------------------------------------------------- 1 | @import "sassy-buttons"; 2 | 3 | // Sassy Button defaults 4 | // These are the base defaults for the buttons, if you are going to use similar buttons on an entire site, 5 | // overriding these can save you some time by calling the sassy button mixin with less arguments. 6 | 7 | $sb-base-color: rgba(11, 153, 194, 1); 8 | $sb-second-color: false; 9 | $sb-border-radius: 5px; 10 | $sb-padding: 0.3em 1.5em; 11 | $sb-font-size: 12px; 12 | $sb-text-color: white; 13 | $sb-text-style: "inset"; 14 | $sb-gradient-style: "simple"; 15 | $sb-pseudo-states: true; 16 | $sb-ie-support: true; 17 | 18 | // Mixin that gets included when calling the sassy-button-structure if you need any 19 | // styles applied to all your sassy buttons, add it here. 20 | 21 | @mixin sassy-button-default-structure { 22 | display: inline-block; 23 | cursor: pointer; 24 | } 25 | 26 | // * Mixin reference 27 | // * ----------------------------------------- 28 | // * @include sassy-button(gradient-style, border-radius, font-size, first-color, second-color, text-color, text-style, auto-states, ie-support); 29 | // * @include sassy-button-structure(-border-radius, font-size, padding); 30 | // * @include sassy-button-gradient(gradient-style, first-color, second-color, text-color, text-style, auto-states, ie-support); 31 | -------------------------------------------------------------------------------- /static/stylesheets/.sass-cache/49b65b430aee62cf132001fd9def67cd0781a8ac/WebElements.sassc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timothycrosley/thedom/73833110a298456a1f24ab8e4f5cec09a0655c2d/static/stylesheets/.sass-cache/49b65b430aee62cf132001fd9def67cd0781a8ac/WebElements.sassc -------------------------------------------------------------------------------- /static/stylesheets/.sass-cache/49b65b430aee62cf132001fd9def67cd0781a8ac/_CodeDocumentation.scssc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timothycrosley/thedom/73833110a298456a1f24ab8e4f5cec09a0655c2d/static/stylesheets/.sass-cache/49b65b430aee62cf132001fd9def67cd0781a8ac/_CodeDocumentation.scssc -------------------------------------------------------------------------------- /static/stylesheets/_CodeDocumentation.scss: -------------------------------------------------------------------------------- 1 | .highlight .hll { background-color: #49483e } 2 | .highlight { background: #272822; color: #f8f8f2 } 3 | .highlight .c { color: #75715e } /* Comment */ 4 | .highlight .err { color: #960050; background-color: #1e0010 } /* Error */ 5 | .highlight .k { color: #66d9ef } /* Keyword */ 6 | .highlight .l { color: #ae81ff } /* Literal */ 7 | .highlight .n { color: #f8f8f2 } /* Name */ 8 | .highlight .o { color: #f92672 } /* Operator */ 9 | .highlight .p { color: #f8f8f2 } /* Punctuation */ 10 | .highlight .cm { color: #75715e } /* Comment.Multiline */ 11 | .highlight .cp { color: #75715e } /* Comment.Preproc */ 12 | .highlight .c1 { color: #75715e } /* Comment.Single */ 13 | .highlight .cs { color: #75715e } /* Comment.Special */ 14 | .highlight .ge { font-style: italic } /* Generic.Emph */ 15 | .highlight .gs { font-weight: bold } /* Generic.Strong */ 16 | .highlight .kc { color: #66d9ef } /* Keyword.Constant */ 17 | .highlight .kd { color: #66d9ef } /* Keyword.Declaration */ 18 | .highlight .kn { color: #f92672 } /* Keyword.Namespace */ 19 | .highlight .kp { color: #66d9ef } /* Keyword.Pseudo */ 20 | .highlight .kr { color: #66d9ef } /* Keyword.Reserved */ 21 | .highlight .kt { color: #66d9ef } /* Keyword.Type */ 22 | .highlight .ld { color: #e6db74 } /* Literal.Date */ 23 | .highlight .m { color: #ae81ff } /* Literal.Number */ 24 | .highlight .s { color: #e6db74 } /* Literal.String */ 25 | .highlight .na { color: #a6e22e } /* Name.Attribute */ 26 | .highlight .nb { color: #f8f8f2 } /* Name.Builtin */ 27 | .highlight .nc { color: #a6e22e } /* Name.Class */ 28 | .highlight .no { color: #66d9ef } /* Name.Constant */ 29 | .highlight .nd { color: #a6e22e } /* Name.Decorator */ 30 | .highlight .ni { color: #f8f8f2 } /* Name.Entity */ 31 | .highlight .ne { color: #a6e22e } /* Name.Exception */ 32 | .highlight .nf { color: #a6e22e } /* Name.Function */ 33 | .highlight .nl { color: #f8f8f2 } /* Name.Label */ 34 | .highlight .nn { color: #f8f8f2 } /* Name.Namespace */ 35 | .highlight .nx { color: #a6e22e } /* Name.Other */ 36 | .highlight .py { color: #f8f8f2 } /* Name.Property */ 37 | .highlight .nt { color: #f92672 } /* Name.Tag */ 38 | .highlight .nv { color: #f8f8f2 } /* Name.Variable */ 39 | .highlight .ow { color: #f92672 } /* Operator.Word */ 40 | .highlight .w { color: #f8f8f2 } /* Text.Whitespace */ 41 | .highlight .mf { color: #ae81ff } /* Literal.Number.Float */ 42 | .highlight .mh { color: #ae81ff } /* Literal.Number.Hex */ 43 | .highlight .mi { color: #ae81ff } /* Literal.Number.Integer */ 44 | .highlight .mo { color: #ae81ff } /* Literal.Number.Oct */ 45 | .highlight .sb { color: #e6db74 } /* Literal.String.Backtick */ 46 | .highlight .sc { color: #e6db74 } /* Literal.String.Char */ 47 | .highlight .sd { color: #e6db74 } /* Literal.String.Doc */ 48 | .highlight .s2 { color: #e6db74 } /* Literal.String.Double */ 49 | .highlight .se { color: #ae81ff } /* Literal.String.Escape */ 50 | .highlight .sh { color: #e6db74 } /* Literal.String.Heredoc */ 51 | .highlight .si { color: #e6db74 } /* Literal.String.Interpol */ 52 | .highlight .sx { color: #e6db74 } /* Literal.String.Other */ 53 | .highlight .sr { color: #e6db74 } /* Literal.String.Regex */ 54 | .highlight .s1 { color: #e6db74 } /* Literal.String.Single */ 55 | .highlight .ss { color: #e6db74 } /* Literal.String.Symbol */ 56 | .highlight .bp { color: #f8f8f2 } /* Name.Builtin.Pseudo */ 57 | .highlight .vc { color: #f8f8f2 } /* Name.Variable.Class */ 58 | .highlight .vg { color: #f8f8f2 } /* Name.Variable.Global */ 59 | .highlight .vi { color: #f8f8f2 } /* Name.Variable.Instance */ 60 | .highlight .il { color: #ae81ff } /* Literal.Number.Integer.Long */ 61 | -------------------------------------------------------------------------------- /static/stylesheets/ie.css: -------------------------------------------------------------------------------- 1 | /* Welcome to Compass. Use this file to write IE specific override styles. 2 | * Import this file using the following HTML or equivalent: 3 | * */ 6 | -------------------------------------------------------------------------------- /static/stylesheets/print.css: -------------------------------------------------------------------------------- 1 | /* Welcome to Compass. Use this file to define print styles. 2 | * Import this file using the following HTML or equivalent: 3 | * */ 4 | -------------------------------------------------------------------------------- /static/stylesheets/screen.css: -------------------------------------------------------------------------------- 1 | /* Welcome to Compass. 2 | * In this file you should write your main styles. (or centralize your imports) 3 | * Import this file using the following HTML or equivalent: 4 | * */ 5 | /* line 17, ../../../../../../../usr/lib64/ruby/gems/1.9.1/gems/compass-0.12.2/frameworks/compass/stylesheets/compass/reset/_utilities.scss */ 6 | html, body, div, span, applet, object, iframe, 7 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 8 | a, abbr, acronym, address, big, cite, code, 9 | del, dfn, em, img, ins, kbd, q, s, samp, 10 | small, strike, strong, sub, sup, tt, var, 11 | b, u, i, center, 12 | dl, dt, dd, ol, ul, li, 13 | fieldset, form, label, legend, 14 | table, caption, tbody, tfoot, thead, tr, th, td, 15 | article, aside, canvas, details, embed, 16 | figure, figcaption, footer, header, hgroup, 17 | menu, nav, output, ruby, section, summary, 18 | time, mark, audio, video { 19 | margin: 0; 20 | padding: 0; 21 | border: 0; 22 | font: inherit; 23 | font-size: 100%; 24 | vertical-align: baseline; 25 | } 26 | 27 | /* line 22, ../../../../../../../usr/lib64/ruby/gems/1.9.1/gems/compass-0.12.2/frameworks/compass/stylesheets/compass/reset/_utilities.scss */ 28 | html { 29 | line-height: 1; 30 | } 31 | 32 | /* line 24, ../../../../../../../usr/lib64/ruby/gems/1.9.1/gems/compass-0.12.2/frameworks/compass/stylesheets/compass/reset/_utilities.scss */ 33 | ol, ul { 34 | list-style: none; 35 | } 36 | 37 | /* line 26, ../../../../../../../usr/lib64/ruby/gems/1.9.1/gems/compass-0.12.2/frameworks/compass/stylesheets/compass/reset/_utilities.scss */ 38 | table { 39 | border-collapse: collapse; 40 | border-spacing: 0; 41 | } 42 | 43 | /* line 28, ../../../../../../../usr/lib64/ruby/gems/1.9.1/gems/compass-0.12.2/frameworks/compass/stylesheets/compass/reset/_utilities.scss */ 44 | caption, th, td { 45 | text-align: left; 46 | font-weight: normal; 47 | vertical-align: middle; 48 | } 49 | 50 | /* line 30, ../../../../../../../usr/lib64/ruby/gems/1.9.1/gems/compass-0.12.2/frameworks/compass/stylesheets/compass/reset/_utilities.scss */ 51 | q, blockquote { 52 | quotes: none; 53 | } 54 | /* line 103, ../../../../../../../usr/lib64/ruby/gems/1.9.1/gems/compass-0.12.2/frameworks/compass/stylesheets/compass/reset/_utilities.scss */ 55 | q:before, q:after, blockquote:before, blockquote:after { 56 | content: ""; 57 | content: none; 58 | } 59 | 60 | /* line 32, ../../../../../../../usr/lib64/ruby/gems/1.9.1/gems/compass-0.12.2/frameworks/compass/stylesheets/compass/reset/_utilities.scss */ 61 | a img { 62 | border: none; 63 | } 64 | 65 | /* line 116, ../../../../../../../usr/lib64/ruby/gems/1.9.1/gems/compass-0.12.2/frameworks/compass/stylesheets/compass/reset/_utilities.scss */ 66 | article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section, summary { 67 | display: block; 68 | } 69 | -------------------------------------------------------------------------------- /tests/benchmark_thedom.py: -------------------------------------------------------------------------------- 1 | ''' 2 | benchmark_thedom.py 3 | 4 | Benchmarks the performance of the thedom library, specifically how fast 5 | it will generate complex HTML pages 6 | 7 | Copyright (C) 2015 Timothy Edmund Crosley 8 | 9 | This program is free software; you can redistribute it and/or 10 | modify it under the terms of the GNU General Public License 11 | as published by the Free Software Foundation; either version 2 12 | of the License, or (at your option) any later version. 13 | 14 | This program is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with this program; if not, write to the Free Software 21 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 22 | ''' 23 | 24 | try: 25 | import cPickle as pickle 26 | except ImportError: 27 | import pickle 28 | 29 | import gc 30 | import sys 31 | import time 32 | 33 | from thedom import DictUtils, UITemplate 34 | from thedom.All import DOM, Factory 35 | from thedom.Base import Node, TemplateElement, TextNode 36 | from thedom.Layout import Box 37 | from thedom.MultiplePythonSupport import * 38 | from thedom.Resources import ScriptContainer 39 | 40 | results = {'loopedCreate':0.0, 'loopedInit':0.0, 'loopedToHtml':0.0, 'bigTable':0.0, 'bigTableSize':0.0, 41 | 'createAllOnce':0.0, 'longestCreationTime':0.0, 'nestedNodeCreation':0.0, 42 | 'templateInit':0.0, 'templateToHtml':0.0, 'templateToHtmlSize':0.0, 'templateCreate':0.0} 43 | 44 | def doneSection(): 45 | sys.stdout.write(".") 46 | sys.stdout.flush() 47 | 48 | def getSingleElementGenerationTimes(): 49 | generationTimes = DictUtils.OrderedDict() 50 | for product in Factory.products.keys(): 51 | if "." in product: 52 | continue 53 | doneSection() 54 | startTime = time.time() 55 | scripts = ScriptContainer() 56 | element = Factory.build(product, 'Test', 'Product') 57 | element.setScriptContainer(scripts) 58 | html = element.toHTML() 59 | html += scripts.toHTML() 60 | 61 | generationTime = time.time() - startTime 62 | results['createAllOnce'] += generationTime 63 | generationTimes[generationTime] = (product, len(html)) 64 | results['longestCreationTime'] = generationTimes.orderedKeys[-1] 65 | return generationTimes 66 | 67 | def getGenerationTimeForAllElementsLooped100Times(): 68 | startTime = time.time() 69 | allProducts = Box('AllProducts') 70 | scripts = ScriptContainer() 71 | allProducts.setScriptContainer(scripts) 72 | for x in xrange(100): 73 | doneSection() 74 | results['loopedCreate'] = results['loopedInit'] + results['loopedToHtml'] 75 | for product in Factory.products.keys(): 76 | allProducts.add(Factory.build(product, 'Test', 'Product')) 77 | instantiationTime = time.time() - startTime 78 | results['loopedInit'] = instantiationTime 79 | 80 | startTime = time.time() 81 | html = allProducts.toHTML() 82 | html += scripts.toHTML() 83 | generationTime = (time.time() - startTime) 84 | results['loopedToHtml'] = generationTime 85 | results['loopedToHtmlSize'] = len(html) 86 | results['loopedCreate'] = results['loopedInit'] + results['loopedToHtml'] 87 | 88 | def getTemplateGenerationTimes(): 89 | template = "div#AllProducts\n" 90 | templateElements = [] 91 | for product in Factory.products.keys(): 92 | template += " > %(product)s#test%(product)s\n" % {'product': product} 93 | template = UITemplate.fromSHPAML(template) 94 | 95 | startTime = time.time() 96 | for x in xrange(100): 97 | templateElement = TemplateElement(template=template, factory=Factory) 98 | templateElement.setScriptContainer(templateElement.add(ScriptContainer())) 99 | templateElements.append(templateElement) 100 | doneSection() 101 | results['templateInit'] = time.time() - startTime 102 | 103 | html = "" 104 | startTime = time.time() 105 | for templateElement in templateElements: 106 | html += templateElement.toHTML() 107 | doneSection() 108 | 109 | generationTime = (time.time() - startTime) 110 | results['templateToHtml'] = generationTime 111 | results['templateToHtmlSize'] = len(html) 112 | results['templateCreate'] = results['templateInit'] + results['templateToHtml'] 113 | 114 | def getBigTableGenerationTime(): 115 | template = UITemplate.fromSHPAML("> dom-table#bigTableTest") 116 | table = [dict(a=1, b=2, c=3, d=4, e=5, f=6, g=7, h=8, i=9, j=10) for x in xrange(1000)] 117 | 118 | startTime = time.time() 119 | templateElement = TemplateElement(template=template, factory=Factory) 120 | for rowData in table: 121 | row = templateElement.bigTableTest.add(DOM.TR()) 122 | for data in itervalues(rowData): 123 | row.add(DOM.TD()).add(TextNode(data)) 124 | doneSection() 125 | html = templateElement.toHTML() 126 | results['bigTable'] = time.time() - startTime 127 | results['bigTableSize'] = len(html) 128 | 129 | def getNestedElementTime(): 130 | startTime = time.time() 131 | rootElement = Node('root') 132 | element = rootElement 133 | element._tagName = "root" 134 | html = "" 135 | for x in xrange(900): 136 | doneSection() 137 | element = element.add(Node("element" + str(x))) 138 | element._tagName = 'tag' + str(x) 139 | html += element.toHTML() 140 | 141 | results['nestedNodeCreation'] = time.time() - startTime 142 | results['nestedNodeSize'] = len(html) 143 | 144 | if __name__ == "__main__": 145 | sys.stdout.write("Benchmarking .") 146 | doneSection() 147 | getGenerationTimeForAllElementsLooped100Times() 148 | gc.collect() 149 | doneSection() 150 | getTemplateGenerationTimes() 151 | gc.collect() 152 | results['generationTimes'] = getSingleElementGenerationTimes() 153 | gc.collect() 154 | doneSection() 155 | getNestedElementTime() 156 | gc.collect() 157 | doneSection() 158 | getBigTableGenerationTime() 159 | 160 | print(".") 161 | 162 | print("######## Indvidual element generation times ########") 163 | results['generationTimes'].orderedKeys.sort() 164 | for generationTime, info in iteritems(results['generationTimes']): 165 | print(" Generating html for %s took %s seconds and produced %d len html" % (info[0], generationTime, info[1])) 166 | print(" Total Time: %s" % results['createAllOnce']) 167 | 168 | print("######## Looped creation time (%d elements) ########" % (len(Factory.products.keys()) * 100)) 169 | print(" Instantiating Elements: " + str(results['loopedInit'])) 170 | print(" Generating Html: " + str(results['loopedToHtml'])) 171 | print(" Html Size: " + str(results['loopedToHtmlSize'] / 1024.0 / 1024.0) + " MB") 172 | print(" Total Time:" + str(results['loopedCreate'])) 173 | 174 | print("######## Template creation time (%d elements) ########" % (len(Factory.products.keys()) * 100)) 175 | print(" Instantiating Template: " + str(results['templateInit'])) 176 | print(" Generating Html: " + str(results['templateToHtml'])) 177 | print(" Html Size: " + str(results['templateToHtmlSize'] / 1024.0 / 1024.0) + " MB") 178 | print(" Total Time:" + str(results['templateCreate'])) 179 | 180 | print("######## Nested element generation #########") 181 | print(" Generating 900 nested elements took: " + str(generationTime)) 182 | print(" Html Size: " + str(results['nestedNodeSize'])) 183 | 184 | print("######## Big table generation #########") 185 | print(" Generating a 10X1000 table took: " + str(results['bigTable'])) 186 | print(" Html Size: " + str(results['bigTableSize'] / 1024.0 / 1024.0) + " MB") 187 | results['nestedGeneration'] = generationTime 188 | 189 | with open(".test_thedom_Benchmark.results", 'w') as resultFile: 190 | resultFile.write(str(pickle.dumps(results))) 191 | -------------------------------------------------------------------------------- /tests/test_buttons.py: -------------------------------------------------------------------------------- 1 | ''' 2 | test_Buttons.py 3 | 4 | Tests the functionality of thedom/Buttons.py 5 | 6 | Copyright (C) 2015 Timothy Edmund Crosley 7 | 8 | This program is free software; you can redistribute it and/or 9 | modify it under the terms of the GNU General Public License 10 | as published by the Free Software Foundation; either version 2 11 | of the License, or (at your option) any later version. 12 | 13 | This program is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with this program; if not, write to the Free Software 20 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 21 | ''' 22 | 23 | from test_Base import ElementTester 24 | from thedom.All import Factory 25 | 26 | 27 | class TestLink(ElementTester): 28 | 29 | def setup_class(self): 30 | self.element = Factory.build("Link", "TestLink") 31 | 32 | def test_destination(self): 33 | assert not self.element.destination() 34 | 35 | self.element.setDestination("www.google.com") 36 | assert self.element.destination() == "www.google.com" 37 | assert self.element.toHTML().find("www.google.com") > 0 38 | 39 | def test_rel(self): 40 | self.element.setProperty('rel', 'test') 41 | assert self.element.attributes['rel'] == 'test' 42 | 43 | def test_text(self): 44 | assert not self.element.text() 45 | self.element.setText("TEXT!") 46 | assert self.element.text() == "TEXT!" 47 | 48 | 49 | class TestPopupLink(ElementTester): 50 | 51 | def setup_class(self): 52 | self.element = Factory.build('PopupLink', 'myPopupLink') 53 | 54 | 55 | class TestButton(ElementTester): 56 | 57 | def setup_class(self): 58 | self.element = Factory.build("Button", name="Test") 59 | self.submitElement = Factory.build("SubmitButton") 60 | 61 | def test_attributes(self): 62 | assert self.element.attributes['type'] == 'button' 63 | assert self.submitElement.attributes['type'] == 'submit' 64 | 65 | 66 | class TestPrintButton(ElementTester): 67 | 68 | def setup_class(self): 69 | self.element = Factory.build('PrintButton', 'test') 70 | 71 | 72 | class TestSubmitButton(ElementTester): 73 | 74 | def setup_class(self): 75 | self.element = Factory.build('SubmitButton', 'test') 76 | 77 | 78 | class TestToggleButton(ElementTester): 79 | 80 | def setup_method(self, obj): 81 | self.element = Factory.build("ToggleButton", "Test") 82 | 83 | def test_attributes(self): 84 | assert self.element.button.id == "Test" 85 | 86 | def test_toggle(self): 87 | assert not self.element.toggled() 88 | assert self.element.toggledState.value() == 'off' 89 | assert not "Pushed" in self.element.button.classes 90 | 91 | self.element.toggle() 92 | assert self.element.toggled() 93 | assert self.element.toggledState.value() == 'on' 94 | assert "Pushed" in self.element.button.classes 95 | 96 | self.element.toggle() 97 | assert not self.element.toggled() 98 | assert self.element.toggledState.value() == 'off' 99 | assert not "Pushed" in self.element.button.classes 100 | 101 | self.element.toggleOff() 102 | assert not self.element.toggled() 103 | assert self.element.toggledState.value() == 'off' 104 | assert not "Pushed" in self.element.button.classes 105 | 106 | self.element.toggleOn() 107 | assert self.element.toggled() 108 | assert self.element.toggledState.value() == 'on' 109 | assert "Pushed" in self.element.button.classes 110 | 111 | def test_value(self): 112 | assert self.element.value() == "" 113 | assert self.element.button.value() == "" 114 | self.element.setValue("I changed the value") 115 | assert self.element.value() == "I changed the value" 116 | assert self.element.button.value() == "I changed the value" 117 | 118 | def test_insertVariablesToggledOn(self): 119 | self.element.insertVariables({'Test:Toggled':"on"}) 120 | assert self.element.toggled() 121 | assert self.element.toggledState.value() == 'on' 122 | assert "Pushed" in self.element.button.classes 123 | 124 | def test_insertVariablesToggledFalse(self): 125 | self.element.insertVariables({'Test:Toggled':"off"}) 126 | assert not self.element.toggled() 127 | assert self.element.toggledState.value() == 'off' 128 | assert not "Pushed" in self.element.button.classes 129 | 130 | 131 | class TestToggleLink(ElementTester): 132 | 133 | def setup_class(self): 134 | self.element = Factory.build('ToggleLink', 'test') 135 | -------------------------------------------------------------------------------- /tests/test_charts.py: -------------------------------------------------------------------------------- 1 | ''' 2 | test_Charts.py 3 | 4 | Tests the functionality of thedom/Charts.py 5 | 6 | Copyright (C) 2015 Timothy Edmund Crosley 7 | 8 | This program is free software; you can redistribute it and/or 9 | modify it under the terms of the GNU General Public License 10 | as published by the Free Software Foundation; either version 2 11 | of the License, or (at your option) any later version. 12 | 13 | This program is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with this program; if not, write to the Free Software 20 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 21 | ''' 22 | 23 | from test_Base import ElementTester 24 | from thedom.All import Factory 25 | 26 | 27 | class ChartTester(ElementTester): 28 | 29 | def setup_class(self): 30 | self.element = None 31 | 32 | def test_loadProperties(self): 33 | variableDict = {'height':200, 34 | 'width':300, 35 | 'meaninglessProperty':None} 36 | 37 | self.element.setProperties(variableDict) 38 | 39 | assert self.element.height() == 200 40 | assert self.element.width() == 300 41 | 42 | 43 | def test_setWidth(self): 44 | self.element.setWidth(400) 45 | assert self.element.width() == 400 46 | 47 | self.element.setWidth(500) 48 | assert self.element.width() == 500 49 | 50 | try: 51 | self.element.setWidth(99999999) 52 | assert False 53 | except ValueError: 54 | assert True 55 | except: 56 | assert False 57 | 58 | assert self.element.width() == 500 59 | 60 | 61 | def test_setHeight(self): 62 | self.element.setHeight(400) 63 | assert self.element.height() == 400 64 | 65 | self.element.setHeight(500) 66 | assert self.element.height() == 500 67 | 68 | try: 69 | self.element.setHeight(99999999) 70 | assert False 71 | except ValueError: 72 | assert True 73 | except: 74 | assert False 75 | 76 | assert self.element.height() == 500 77 | 78 | 79 | class TestPieChart(ChartTester): 80 | 81 | def setup_class(self): 82 | self.element = Factory.build("Charts-PieChart") 83 | 84 | 85 | class TestPieChart3D(ChartTester): 86 | 87 | def setup_class(self): 88 | self.element = Factory.build("Charts-PieChart3D") 89 | 90 | 91 | class TestHorizontalBarChart(ChartTester): 92 | 93 | def setup_class(self): 94 | self.element = Factory.build("Charts-HorizontalBarChart") 95 | 96 | 97 | class TestVerticalBarChart(ChartTester): 98 | 99 | def setup_class(self): 100 | self.element = Factory.build("Charts-VerticalBarChart") 101 | 102 | 103 | class TestLineChart(ChartTester): 104 | 105 | def setup_class(self): 106 | self.element = Factory.build("Charts-LineChart") 107 | 108 | if __name__ == "__main__": 109 | import subprocess 110 | subprocess.Popen("py.test test_Charts.py", shell=True).wait() 111 | -------------------------------------------------------------------------------- /tests/test_connectable.py: -------------------------------------------------------------------------------- 1 | ''' 2 | test_Connectable.py 3 | 4 | Tests the functionality of thedom/Connectable.py 5 | 6 | Copyright (C) 2015 Timothy Edmund Crosley 7 | 8 | This program is free software; you can redistribute it and/or 9 | modify it under the terms of the GNU General Public License 10 | as published by the Free Software Foundation; either version 2 11 | of the License, or (at your option) any later version. 12 | 13 | This program is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with this program; if not, write to the Free Software 20 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 21 | ''' 22 | 23 | from thedom.Connectable import Connectable 24 | 25 | 26 | class Value(Connectable): 27 | signals = ['valueChanged'] 28 | 29 | def __init__(self, value): 30 | super(Value, self).__init__() 31 | self.value = value 32 | 33 | def setValue(self, value): 34 | self.value = value 35 | self.emit('valueChanged', value) 36 | 37 | def clearValue(self): 38 | self.value = "" 39 | 40 | def tooManyParmsToBeSlot(self, param1, param2): 41 | pass 42 | 43 | 44 | class TestConnectable(object): 45 | 46 | def setup_method(self, method): 47 | self.value1 = Value(1) 48 | self.value2 = Value(2) 49 | 50 | def test_incorrectSignalSlotConnections(self): 51 | #emit fake signal 52 | assert self.value1.emit("fake signal") == [] 53 | 54 | #connect using fake signal 55 | assert self.value1.connect("fake signal", None, self, 'setValue') is None 56 | 57 | #connect to fake slot 58 | self.value1.connect("valueChanged", None, self.value1, 'fake slot') 59 | assert self.value1.emit('valueChanged') is False 60 | 61 | def test_connectWithoutCondition(self): 62 | #test without value overide 63 | self.value1.connect('valueChanged', None, self.value2, 'setValue') 64 | self.value1.setValue("This is a test") 65 | assert self.value2.value == "This is a test" 66 | self.value1.disconnect() 67 | 68 | #test with value overide 69 | self.value1.connect('valueChanged', None, 70 | self.value2, 'setValue', 'I changed the value') 71 | self.value1.setValue("This is a test") 72 | assert self.value2.value == "I changed the value" 73 | 74 | def test_connectWithCondition(self): 75 | #test without value overide 76 | self.value1.connect('valueChanged', 'Hello', self.value2, 'setValue') 77 | self.value1.setValue('Goodbye') 78 | assert self.value2.value == 2 79 | self.value1.setValue('Hello') 80 | assert self.value2.value == 'Hello' 81 | self.value1.disconnect() 82 | 83 | #test with value overide 84 | self.value1.connect('valueChanged', 'Hello', 85 | self.value2, 'setValue', 'Goodbye') 86 | self.value1.setValue('Goodbye') 87 | assert self.value2.value == 'Hello' 88 | self.value1.setValue('Hello') 89 | assert self.value2.value == 'Goodbye' 90 | 91 | #Test on slot that takes no arguments 92 | self.value1.connect('valueChanged', 'Die!!', self.value2, 'clearValue') 93 | assert self.value1.emit('valueChanged', 'Die!!') == [None] 94 | self.value1.connect('valueChanged', None, self.value2, 'clearValue') 95 | assert self.value1.emit('valueChanged') == [None] 96 | self.value1.disconnect() 97 | 98 | #Test method with too many params to be a slot 99 | self.value1.connect('valueChanged', 'False', self.value2, 'tooManyParmsToBeSlot') 100 | assert self.value1.emit('valueChanged', 'False') == [''] 101 | 102 | def test_disconnect(self): 103 | self.value1.connect('valueChanged', None, self.value2, 'setValue') 104 | self.value1.setValue('It changes the value') 105 | assert self.value2.value == 'It changes the value' 106 | 107 | self.value1.disconnect('valueChanged', None, self.value2, 'setValue') 108 | self.value1.setValue('But not anymore') 109 | assert self.value2.value == 'It changes the value' 110 | 111 | self.value1.connect('valueChanged', None, self.value2, 'setValue') 112 | self.value1.disconnect('valueChanged', None, self.value2) 113 | self.value1.setValue('Still Wont') 114 | assert self.value2.value == 'It changes the value' 115 | 116 | self.value1.connect('valueChanged', None, self.value2, 'setValue') 117 | self.value1.disconnect('valueChanged') 118 | self.value1.setValue('Still Wont') 119 | assert self.value2.value == 'It changes the value' 120 | -------------------------------------------------------------------------------- /tests/test_container.py: -------------------------------------------------------------------------------- 1 | ''' 2 | test_Container.py 3 | 4 | Tests the functionality of thedom/Container.py 5 | 6 | Copyright (C) 2015 Timothy Edmund Crosley 7 | 8 | This program is free software; you can redistribute it and/or 9 | modify it under the terms of the GNU General Public License 10 | as published by the Free Software Foundation; either version 2 11 | of the License, or (at your option) any later version. 12 | 13 | This program is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with this program; if not, write to the Free Software 20 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 21 | ''' 22 | 23 | from test_Base import ElementTester 24 | from thedom.All import Factory 25 | 26 | 27 | class TestDropDownMenu(ElementTester): 28 | 29 | def setup_class(self): 30 | self.element = Factory.build("dropdownmenu", "Test") 31 | 32 | 33 | class TestAutocomplete(ElementTester): 34 | 35 | def setup_class(self): 36 | self.element = Factory.build("autocomplete", "Test") 37 | 38 | 39 | class TestTab(ElementTester): 40 | 41 | def setup_class(self): 42 | self.element = Factory.build("tab", "Test") 43 | 44 | def test_text(self): 45 | tab = Factory.build("tab", "Test") 46 | assert tab.tabLabel.text() == "" 47 | assert tab.text() == "" 48 | assert tab.text() == tab.tabLabel.text() 49 | 50 | tab.setProperty('text', 'heyy') 51 | assert tab.tabLabel.text() == "heyy" 52 | assert tab.text() == "heyy" 53 | assert tab.text() == tab.tabLabel.text() 54 | 55 | class TestTabContainer(ElementTester): 56 | 57 | def setup_class(self): 58 | self.element = Factory.build('TabContainer', 'Test') 59 | 60 | def test_tabs(self): 61 | tab1 = self.element.add(Factory.build('Tab', 'Tab1')) 62 | tab2 = self.element.add(Factory.build('Tab', 'Tab2')) 63 | tab3 = self.element.add(Factory.build('Tab', 'Tab3')) 64 | assert tab1.isSelected 65 | assert not tab2.isSelected 66 | assert not tab3.isSelected 67 | 68 | tab2.select() 69 | assert not tab1.isSelected 70 | assert tab2.isSelected 71 | assert not tab3.isSelected 72 | 73 | tab3.select() 74 | assert not tab1.isSelected 75 | assert not tab2.isSelected 76 | assert tab3.isSelected 77 | 78 | 79 | class TestAccordion(ElementTester): 80 | 81 | def setup_class(self): 82 | self.element = Factory.build("accordion", "Test") 83 | 84 | 85 | class TestFormContainer(ElementTester): 86 | 87 | def setup_class(self): 88 | self.element = Factory.build("FormContainer", name="Test") 89 | self.element.add(Factory.build("Button", name="Button")) 90 | self.element.add(Factory.build("Flow", name="Container")) 91 | -------------------------------------------------------------------------------- /tests/test_dataviews.py: -------------------------------------------------------------------------------- 1 | ''' 2 | test_DataViews.py 3 | 4 | Tests the functionality of thedom/DataViews.py 5 | 6 | Copyright (C) 2015 Timothy Edmund Crosley 7 | 8 | This program is free software; you can redistribute it and/or 9 | modify it under the terms of the GNU General Public License 10 | as published by the Free Software Foundation; either version 2 11 | of the License, or (at your option) any later version. 12 | 13 | This program is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with this program; if not, write to the Free Software 20 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 21 | ''' 22 | 23 | from test_Base import ElementTester 24 | from thedom.All import Factory 25 | from thedom.DataViews import Table 26 | 27 | 28 | class TestTable(ElementTester): 29 | 30 | class TestColumn(ElementTester): 31 | 32 | def setup_class(self): 33 | self.element = Table.Column("Test") 34 | 35 | def test_text(self): 36 | assert self.element.text() == "" 37 | self.element.setText("I changed the text") 38 | assert self.element.text() == "I changed the text" 39 | assert "I changed the text" in self.element.toHTML() 40 | 41 | def setup_method(self, element): 42 | self.element = Factory.build("Table", "Test") 43 | 44 | def test_attributes(self): 45 | assert self.element.rows == [] 46 | assert self.element.columns == [] 47 | 48 | def test_addRow(self): 49 | row = self.element.addRow() 50 | assert len(self.element.rows) == 1 51 | assert self.element.columns == [] 52 | 53 | assert not row.actualCell("Name") 54 | assert self.element.header.childElements == [] 55 | 56 | #Setting a cell with a valid row & not yet created column 57 | #should automatically create the header and column 58 | row.cell("Name").setText("Timothy") 59 | assert row.cell("Name").text() == "Timothy" 60 | assert type(row.actualCell("Name")) == Table.Column 61 | assert len(self.element.header.childElements) == 1 62 | 63 | row.cell("Name").setText("Josh") 64 | assert row.cell("Name").text() == "Josh" 65 | assert type(row.actualCell("Name")) == Table.Column 66 | assert len(self.element.header.childElements) == 1 67 | 68 | row.cell("Type").setText("Person") 69 | assert row.cell("Type").text() == "Person" 70 | assert type(row.actualCell("Type")) == Table.Column 71 | assert len(self.element.header.childElements) == 2 72 | 73 | def test_addRows(self): 74 | # Using a list of dictionaries to populate a table is the most straight-forward method, but looses column 75 | # order 76 | self.element.addRows(({'B':'Row1', 'A':'Row1'}, {'B':'Row2', 'A':'Row2'})) 77 | assert self.element.columns == ['A', 'B'] 78 | 79 | # Using nested tuples when adding rows should allow you to define the order of columns 80 | newTable = Factory.build('Table', 'Test2') 81 | newTable.addRows(((('B','Row1'), ('A','Row1')), (('B','Row2'), ('A','Row2')))) 82 | assert newTable.columns == ['B', 'A'] 83 | 84 | def test_reorderingColumns(self): 85 | self.element.addRows(({'B':'Cell1', 'A':'Cell2'}, {'B':'Cell3', 'A':'Cell4'})) 86 | assert self.element.columns == ['A', 'B'] 87 | assert self.element.rows[0][0][0].text() == "Cell2" 88 | assert self.element.rows[0][1][0].text() == "Cell1" 89 | assert self.element.rows[1][0][0].text() == "Cell4" 90 | assert self.element.rows[1][1][0].text() == "Cell3" 91 | 92 | self.element.columns = ['B', 'A'] 93 | assert self.element.columns == ['B', 'A'] 94 | assert self.element.rows[0][0][0].text() == "Cell1" 95 | assert self.element.rows[0][1][0].text() == "Cell2" 96 | assert self.element.rows[1][0][0].text() == "Cell3" 97 | assert self.element.rows[1][1][0].text() == "Cell4" 98 | 99 | def test_setCell(self): 100 | newRow = self.element.addRow() 101 | newRow.cell("Name").setText("Tim") 102 | newRow.cell("Type").setText("Developer") 103 | newRow.cell("Country").setText("United States") 104 | 105 | assert newRow.cell('Name').text() == "Tim" 106 | assert newRow.cell('Type').text() == "Developer" 107 | assert newRow.cell('Country').text() == "United States" 108 | 109 | def test_appendToCell(self): 110 | newRow = self.element.addRow() 111 | newRow.cell("Name").setText("Tim") 112 | newRow.cell("Type").setText("Developer") 113 | newRow.cell("Country").setText("United") 114 | 115 | assert newRow.cell('Name').text() == "Tim" 116 | assert newRow.cell('Type').text() == "Developer" 117 | assert newRow.cell('Country').text() == "United" 118 | 119 | newRow.cell("Name").appendText("Crosley") 120 | newRow.cell("Country").appendText("States") 121 | 122 | assert newRow.cell('Name').text() == "Tim" # Appended text is only visible by going through label tree 123 | assert newRow.cell('Type').text() == "Developer" 124 | assert newRow.cell('Country').text() == "United" 125 | 126 | def test_addColumn(self): 127 | column1 = self.element.addColumn("Name") 128 | column2 = self.element.addColumn("Type") 129 | column3 = self.element.addColumn("Super Powers") 130 | assert len(self.element.rows) == 0 131 | assert self.element.columns == ["Name", "Type", "Super Powers"] 132 | assert len(self.element.header.childElements) == 3 133 | 134 | # assert it returns existing column if it is added a second 135 | column1again = self.element.addColumn("Name") 136 | column2again = self.element.addColumn("Type") 137 | assert column1again == column1 138 | assert column2again == column2 139 | 140 | row = self.element.addRow() 141 | assert len(self.element.rows) == 1 142 | assert row.cell("Name").text() == "" 143 | assert row.cell("Type").text() == "" 144 | assert row.cell("Super Powers").text() == "" 145 | assert type(row.actualCell("Name")) == Table.Column 146 | assert type(row.actualCell("Type")) == Table.Column 147 | assert type(row.actualCell("Super Powers")) == Table.Column 148 | 149 | row.cell("Name").setText("Timothy") 150 | row.cell("Type").setText("Person") 151 | row.cell("Super Powers").setText("Bassically all of them.") 152 | assert row.cell("Name").text() == "Timothy" 153 | assert row.cell("Type").text() == "Person" 154 | assert row.cell("Super Powers").text() == "Bassically all of them." 155 | 156 | self.element.addColumn("Lied About Super Powers") 157 | assert self.element.columns == ["Name", "Type", "Super Powers", 158 | "Lied About Super Powers"] 159 | assert len(self.element.header.childElements) == 4 160 | 161 | assert row.cell("Lied About Super Powers").text() == "" 162 | row.cell("Lied About Super Powers").setText("Ok Maybee a little bit...") 163 | assert row.cell("Lied About Super Powers").text() == "Ok Maybee a little bit..." 164 | 165 | def test_setProperties(self): 166 | data = (("columns", ["Name", "Type", "Location"]), 167 | ("rows", [{"Name":"start.bin", 168 | "Type":"Binary", 169 | "Location":"/usr/bin"}, 170 | {"Name":"document.txt", 171 | "Type":"Text File", 172 | "Location":"~/documents"}])) 173 | 174 | self.element.setProperties(data) 175 | assert len(self.element.rows) == 2 176 | assert self.element.columns == ["Name", "Type", "Location"] 177 | assert len(self.element.header.childElements) == 3 178 | self.element.cell(0, "Name").setText("start.bin") 179 | self.element.cell(0, "Type").setText("Binary") 180 | self.element.cell(0, "Location").setText("/usr/bin") 181 | self.element.cell(1, "Name").setText("document.txt") 182 | self.element.cell(1, "Type").setText("Text File") 183 | self.element.cell(1, "Location").setText("~/documents") 184 | 185 | 186 | class TestStoredValue(ElementTester): 187 | 188 | def setup_class(self): 189 | self.element = Factory.build("storedValue", "Test", "Test") 190 | -------------------------------------------------------------------------------- /tests/test_dict_utils.py: -------------------------------------------------------------------------------- 1 | ''' 2 | test_DictUtils.py 3 | 4 | Tests the functionality of thedom/DictUtils.py 5 | 6 | Copyright (C) 2015 Timothy Edmund Crosley 7 | 8 | This program is free software; you can redistribute it and/or 9 | modify it under the terms of the GNU General Public License 10 | as published by the Free Software Foundation; either version 2 11 | of the License, or (at your option) any later version. 12 | 13 | This program is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with this program; if not, write to the Free Software 20 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 21 | ''' 22 | 23 | from thedom import DictUtils 24 | 25 | 26 | class TestDictUtilsTest(object): 27 | def __init__(self, needsdb=1, db=None): 28 | pass 29 | 30 | def test_MissingKey(self): 31 | assert (DictUtils.missingKey({}, {}) == []) 32 | assert (DictUtils.missingKey({'A':'1'}, {'A':'1'}) == []) 33 | assert (DictUtils.missingKey({'A':'1'}, {}) == [{'A':'1'}]) 34 | assert (DictUtils.missingKey({}, {'A':'1'}) == [{'A':'1'}]) 35 | 36 | def test_DictCompare(self): 37 | assert (DictUtils.dictCompare({}, {}) == []) 38 | assert (DictUtils.dictCompare({'A':'1'}, {'A':'1'}) == []) 39 | assert (DictUtils.dictCompare({'A':'1'}, {}) == [{'A':'1'}]) 40 | assert (DictUtils.dictCompare({}, {'A':'1'}) == [{'A':'1'}]) 41 | assert (DictUtils.dictCompare({'A':'0'}, {'A':'1'}) == [{'A':'0->1'}]) 42 | assert (DictUtils.dictCompare({'A':'0', 'B':'2'}, {'A':'1'}) == [{'B':'2'}, {'A':'0->1'}]) 43 | assert (DictUtils.dictCompare({'A':'0'}, {'A':'1', 'B':'2'}) == [{'B':'2'}, {'A':'0->1'}]) 44 | 45 | def test_UserInputStrip(self): 46 | assert (DictUtils.userInputStrip({}) == {}) 47 | assert (DictUtils.userInputStrip({'a':' b '}) == {'a':'b'}) 48 | assert (DictUtils.userInputStrip({'a':None}) == {'a':None}) 49 | assert (DictUtils.userInputStrip({'a':1}) == {'a':1}) 50 | assert (DictUtils.userInputStrip({'a':[' beta ']}) == {'a':[' beta ']}) 51 | 52 | def test_SetNestedValue(self): 53 | d = {} 54 | DictUtils.setNestedValue(d, 'hello', 'world') 55 | assert (d == {'hello':'world'}) 56 | d = {'hello':'goodbye'} 57 | assert (d == {'hello':'goodbye'}) 58 | DictUtils.setNestedValue(d, 'hello', 'world') 59 | assert (d == {'hello':'world'}) 60 | d = {} 61 | DictUtils.setNestedValue(d, 'hello.there', 'world') 62 | assert (d == {'hello':{'there':'world'}}) 63 | d = {'hello':{'there':'goodbye'}} 64 | assert (d == {'hello':{'there':'goodbye'}}) 65 | DictUtils.setNestedValue(d, 'hello.there', 'world') 66 | assert (d == {'hello':{'there':'world'}}) 67 | 68 | def test_GetNestedValue(self): 69 | d = {} 70 | assert (DictUtils.getNestedValue(d, 'hello') == None) 71 | d = {'hello':{'there':'goodbye'}} 72 | assert (DictUtils.getNestedValue(d, 'hello') == {'there':'goodbye'}) 73 | assert (DictUtils.getNestedValue(d, 'hello.there') == 'goodbye') 74 | 75 | def test_createDictFromString(self): 76 | string = "hello[-EQUALS-]goodbye[-AND-]monkey[-EQUALS-]tim[-AND-]tim[-EQUALS-]monkey" 77 | assert (DictUtils.createDictFromString(string, '[-AND-]', '[-EQUALS-]') == 78 | {'hello':'goodbye', 'monkey':'tim', 'tim':'monkey'}) 79 | 80 | def test_NestedDict(self): 81 | nd = DictUtils.NestedDict() 82 | assert (nd != None) 83 | assert (nd.keys() == []) 84 | nd.setValue('hello', 'world') 85 | assert (nd.keys() == ['hello']) 86 | assert (nd['hello'] == 'world') 87 | assert (nd.getValue('hello') == 'world') 88 | assert ( nd.getValue != 'jello') 89 | assert (nd.getValue('jello', default='yellow') == 'yellow') 90 | nd = DictUtils.NestedDict() 91 | assert (nd != None) 92 | nd.setValue('hello.there', 'world') 93 | assert (nd.keys() == ['hello']) 94 | assert (nd['hello'] == {'there':'world'}) 95 | assert (nd.getValue('hello') == {'there':'world'}) 96 | assert (nd.getValue('hello.there') == 'world') 97 | assert ( nd.getValue != 'hello.bear') 98 | assert (nd.getValue('hello.bear', default='grizzly') == 'grizzly') 99 | nd = DictUtils.NestedDict({}) 100 | assert (nd != None) 101 | assert (nd.keys() == []) 102 | nd = DictUtils.NestedDict({'alpha':'beta'}) 103 | assert (nd.keys() == ['alpha']) 104 | assert (nd.getValue('alpha') == 'beta') 105 | nd = DictUtils.NestedDict({'alpha':{'beta':'gamma'}}) 106 | assert (nd.keys() == ['alpha']) 107 | assert (nd.getValue('alpha') == {'beta':'gamma'}) 108 | assert (nd.getValue('alpha.beta') == 'gamma') 109 | 110 | #test difference 111 | myNestedDict = DictUtils.NestedDict() 112 | myNestedDict.setValue("dude.1.name", "Tim") 113 | myNestedDict.setValue("dude.2.name", "Tim2") 114 | myNestedDict.setValue("dude.3.name", "Mike") 115 | myNestedDict.setValue("dude.1.type", "Monkey") 116 | myNestedDict.setValue("dudette.1.name", "Susan") 117 | myNestedDict.setValue("dudette.2.name", "Karen") 118 | 119 | myNestedDict2 = DictUtils.NestedDict() 120 | myNestedDict2.setValue("dude.1.name", "Mike") 121 | myNestedDict2.setValue("dude.2.name", "Tim2") 122 | myNestedDict2.setValue("dude.3.name", "Tim") 123 | myNestedDict2.setValue("dude.3.type", "Monkey") 124 | myNestedDict2.setValue("dudette.1.name", "Susan") 125 | myNestedDict2.setValue("dudette.2.name", "Stella") 126 | 127 | assert myNestedDict.allKeys() == ['dudette.1.name', 'dudette.2.name', 'dude.1.type', 128 | 'dude.1.name', 'dude.3.name', 'dude.2.name'] 129 | assert myNestedDict2.allKeys() == ['dudette.1.name', 'dudette.2.name', 'dude.1.name', 130 | 'dude.3.type', 'dude.3.name', 'dude.2.name'] 131 | assert myNestedDict.difference(myNestedDict2) == [('dudette.2.name', 'Karen', 'Stella'), 132 | ('dude.1.type', 'Monkey', None), 133 | ('dude.1.name', 'Tim', 'Mike'), 134 | ('dude.3.type', None, 'Monkey'), 135 | ('dude.3.name', 'Mike', 'Tim')] 136 | -------------------------------------------------------------------------------- /tests/test_display.py: -------------------------------------------------------------------------------- 1 | ''' 2 | test_Display.py 3 | 4 | Tests the functionality of thedom/Display.py 5 | 6 | Copyright (C) 2015 Timothy Edmund Crosley 7 | 8 | This program is free software; you can redistribute it and/or 9 | modify it under the terms of the GNU General Public License 10 | as published by the Free Software Foundation; either version 2 11 | of the License, or (at your option) any later version. 12 | 13 | This program is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with this program; if not, write to the Free Software 20 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 21 | ''' 22 | 23 | from test_Base import ElementTester 24 | from thedom.All import Factory 25 | 26 | 27 | class TestImage(ElementTester): 28 | 29 | def setup_method(self, element): 30 | self.element = Factory.build("Image", name="Test") 31 | 32 | def test_setProperties(self): 33 | self.element.setProperties({'src':'images/lightbulb.png'}) 34 | assert self.element.attributes['src'] == 'images/lightbulb.png' 35 | 36 | def test_value(self): 37 | assert not self.element.attributes.get('src', None) 38 | self.element.setProperty('src', "images/lightbulb.png") 39 | assert self.element.attributes['src'] == "images/lightbulb.png" 40 | 41 | 42 | class TestList(ElementTester): 43 | 44 | def setup_class(self): 45 | self.element = Factory.build('List', 'testList') 46 | 47 | 48 | class TestLabel(object): 49 | 50 | def setup_method(self, method): 51 | self.element = Factory.build("Label", "Test") 52 | 53 | def test_text(self): 54 | assert not self.element.text() 55 | self.element.setText("I changed the text") 56 | assert self.element.text() == "I changed the text" 57 | assert "I changed the text" in self.element.toHTML() 58 | 59 | def test_setProperties(self): 60 | assert self.element.text() == "" 61 | self.element.setProperties({'text':'I set the text'}) 62 | assert self.element.text() == "I set the text" 63 | assert "I set the text" in self.element.toHTML() 64 | 65 | 66 | class TestParagraph(TestLabel): 67 | 68 | def setup_method(self, method): 69 | self.element = Factory.build("Paragraph", "Test") 70 | 71 | 72 | class TestSubscript(TestLabel): 73 | 74 | def setup_method(self, method): 75 | self.element = Factory.build("Subscript", "Test") 76 | 77 | 78 | class TestSuperscript(TestLabel): 79 | 80 | def setup_method(self, method): 81 | self.element = Factory.build("Superscript", "Test") 82 | 83 | 84 | class TestPreformattedText(TestLabel): 85 | 86 | def setup_method(self, method): 87 | self.element = Factory.build("PreformattedText", "Test") 88 | 89 | 90 | class TestFormError(ElementTester): 91 | 92 | def setup_class(self): 93 | self.element = Factory.build("formError", name="TestFormError") 94 | 95 | 96 | class TestEmpty(ElementTester): 97 | 98 | def setup_class(self): 99 | self.element = Factory.build("box") 100 | self.element.add(Factory.build("empty", name="Test")) 101 | -------------------------------------------------------------------------------- /tests/test_factory.py: -------------------------------------------------------------------------------- 1 | ''' 2 | test_Factory.py 3 | 4 | Tests the functionality of thedom/Factory.py 5 | 6 | Copyright (C) 2015 Timothy Edmund Crosley 7 | 8 | This program is free software; you can redistribute it and/or 9 | modify it under the terms of the GNU General Public License 10 | as published by the Free Software Foundation; either version 2 11 | of the License, or (at your option) any later version. 12 | 13 | This program is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with this program; if not, write to the Free Software 20 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 21 | ''' 22 | 23 | from thedom.All import Factory 24 | from thedom.Base import Invalid, Node 25 | from thedom.Factory import Composite as CompositeFactory 26 | from thedom.Factory import Factory as FactoryClass 27 | from thedom.UITemplate import Template 28 | 29 | 30 | class FakeNode(Node): 31 | def __init__(self, id=None, name=None, parent=None): 32 | Node.__init__(self, "Id", "Name") 33 | 34 | 35 | class TestFactory(object): 36 | 37 | def setup_class(self): 38 | baseElement = Factory.build("box") 39 | baseElement.setPrefix("myPrefix-") 40 | self.base = baseElement 41 | 42 | def test_build(self): 43 | """test to ensure using the factory to create a webelement object works""" 44 | createdObject = Factory.build("textbox", "myId", "myName") 45 | self.base.add(createdObject) 46 | 47 | assert createdObject.fullId() == "myPrefix-myId" 48 | assert createdObject.fullName() == "myPrefix-myName" 49 | assert createdObject.parent == self.base 50 | 51 | def test_buildNonExistant(self): 52 | """test to ensure that an attempt to build an non existant webElement 53 | will create an Invalid webElement""" 54 | createdObject = Factory.build("SomeRandomThingThatDoesNotExist") 55 | assert type(createdObject) == Invalid 56 | 57 | def test_addProduct(self): 58 | """test to ensure adding a new webelement to the factory works""" 59 | class FakeNode(Node): 60 | def __init__(self, id=None, name=None, parent=None): 61 | Node.__init__(self, "Id", "Name") 62 | 63 | Factory.addProduct(FakeNode) 64 | 65 | createdObject = Factory.build("fakenode") 66 | self.base.add(createdObject) 67 | 68 | assert createdObject.fullId() == "myPrefix-Id" 69 | assert createdObject.fullName() == "myPrefix-Name" 70 | assert createdObject.parent == self.base 71 | 72 | def test_buildFromTemplate(self): 73 | """test to ensure creating a webelement from a dictionary works""" 74 | 75 | #Try invalid input 76 | assert type(Factory.buildFromTemplate(None)) == Invalid 77 | assert type(Factory.buildFromTemplate(Template(create=None))) == Invalid 78 | assert type(Factory.buildFromTemplate(Template('SomeElementThatDoesNotExist'))) == Invalid 79 | 80 | template = Template('box', properties=(('style', 'margin:5px;'),), 81 | childElements=(Template('button', id='My Field A', accessor="Field1", 82 | properties=(('style', 'margin-bottom:4px; margin-top:7px; clear:both;'), 83 | ('text', 'Field 1:'))), 84 | Template('button', id='My Field B', 85 | properties=(('style', 'margin-bottom:4px; margin-top:7px; clear:both;'), 86 | ('text', 'Field 2:'))), 87 | Template('label', id='My Field C', 88 | properties=(('style', 'margin-bottom:4px; margin-top:7px; clear:both;'), 89 | ('text', 'Field 3:'))), 90 | )) 91 | 92 | Factory.addProduct(FakeNode) 93 | 94 | accessors = {} 95 | testObject = Factory.buildFromTemplate(template, {'InputField1':"value"}, accessors=accessors) 96 | assert testObject.__class__.__name__ == "Box" 97 | assert testObject.style['margin'] == '5px' 98 | 99 | #Field 1 100 | childElement = testObject.childElements[0] 101 | assert childElement.__class__.__name__ == "Button" 102 | assert childElement.style == \ 103 | {'margin-bottom':'4px', 'margin-top':'7px', 'clear':'both'} 104 | assert childElement.text() == "Field 1:" 105 | assert childElement == accessors['Field1'] 106 | 107 | #Field 2 108 | childElement = testObject.childElements[1] 109 | assert childElement.__class__.__name__ == "Button" 110 | assert childElement.style == {'margin-bottom':'4px', 'margin-top':'7px', 'clear':'both'} 111 | assert childElement.text() == "Field 2:" 112 | 113 | #Field 3 114 | childElement = testObject.childElements[2] 115 | assert childElement.__class__.__name__ == "Label" 116 | assert childElement.style == \ 117 | {'margin-bottom':'4px', 'margin-top':'7px', 'clear':'both'} 118 | assert childElement.text() == "Field 3:" 119 | 120 | 121 | class TestCompositeFactory(object): 122 | 123 | def test_composite(self): 124 | generalMotors = FactoryClass() 125 | class Volt(object): 126 | pass 127 | generalMotors.addProduct(Volt) 128 | 129 | toyota = FactoryClass() 130 | class Prius(object): 131 | pass 132 | toyota.addProduct(Prius) 133 | 134 | # oh no! both companies were bought out! 135 | electricCarCompanyOfTheFuture = CompositeFactory((generalMotors, toyota)) 136 | assert electricCarCompanyOfTheFuture.products == {'volt':Volt, 'prius':Prius} 137 | -------------------------------------------------------------------------------- /tests/test_hidden_inputs.py: -------------------------------------------------------------------------------- 1 | ''' 2 | test_HiddenInputs.py 3 | 4 | Tests the functionality of thedom/HiddenInputs.py 5 | 6 | Copyright (C) 2015 Timothy Edmund Crosley 7 | 8 | This program is free software; you can redistribute it and/or 9 | modify it under the terms of the GNU General Public License 10 | as published by the Free Software Foundation; either version 2 11 | of the License, or (at your option) any later version. 12 | 13 | This program is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with this program; if not, write to the Free Software 20 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 21 | ''' 22 | 23 | from test_Base import ElementTester 24 | from thedom.All import Factory 25 | 26 | 27 | class TestHiddenValue(ElementTester): 28 | 29 | def setup_class(self): 30 | self.element = Factory.build("HiddenValue", name="Test") 31 | 32 | def test_attributes(self): 33 | assert self.element.attributes['type'] == 'hidden' 34 | 35 | def test_text(self): 36 | assert not self.element.text() 37 | assert not self.element.value() 38 | self.element.setValue("I changed the value") 39 | assert self.element.value() == "I changed the value" 40 | assert self.element.text() == "I changed the value" 41 | self.element.setText("I changed the text") 42 | assert self.element.text() == "I changed the text" 43 | assert self.element.value() == "I changed the text" 44 | 45 | 46 | class TestHiddenBooleanValue(ElementTester): 47 | 48 | def setup_class(self): 49 | self.element = Factory.build("HiddenBooleanValue", name="Test") 50 | 51 | def test_value(self): 52 | assert not self.element.text() 53 | assert not self.element.value() 54 | self.element.setValue("1") 55 | assert self.element.value() is True 56 | self.element.setText(True) 57 | assert self.element.value() is True 58 | self.element.setValue("") 59 | assert self.element.value() is False 60 | self.element.setValue(False) 61 | assert self.element.value() is False 62 | 63 | 64 | class TestHiddenIntValue(ElementTester): 65 | 66 | def setup_class(self): 67 | self.element = Factory.build("HiddenIntValue", name="Test") 68 | 69 | def test_value(self): 70 | assert not self.element.text() 71 | assert not self.element.value() 72 | self.element.setValue("1") 73 | assert self.element.value() == 1 74 | assert self.element.text() == 1 75 | self.element.setText("") 76 | assert self.element.text() == 0 77 | assert self.element.value() == 0 78 | self.element.setText(None) 79 | assert self.element.text() == 0 80 | assert self.element.value() == 0 81 | self.element.setText(2) 82 | assert self.element.text() == 2 83 | assert self.element.value() == 2 84 | self.element.setText(0) 85 | assert self.element.text() == 0 86 | assert self.element.value() == 0 87 | -------------------------------------------------------------------------------- /tests/test_iterator_utils.py: -------------------------------------------------------------------------------- 1 | ''' 2 | test_IteratorUtils.py 3 | 4 | Tests the functionality of thedom/IteratorUtils.py 5 | 6 | Copyright (C) 2015 Timothy Edmund Crosley 7 | 8 | This program is free software; you can redistribute it and/or 9 | modify it under the terms of the GNU General Public License 10 | as published by the Free Software Foundation; either version 2 11 | of the License, or (at your option) any later version. 12 | 13 | This program is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with this program; if not, write to the Free Software 20 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 21 | ''' 22 | 23 | from thedom.IteratorUtils import IterableCollection, Queryable, SortedSet 24 | from thedom.MultiplePythonSupport import * 25 | 26 | 27 | def test_iterableCollection(): 28 | """Test basic functionality of iterable collection works as expected""" 29 | collection = IterableCollection() 30 | tims = collection.extend(['Crosley', 'Savannah'], "Timothy") 31 | ryans = collection.extend(['Scaife'], "Ryan") 32 | garys = collection.extend(['Cusimano', 'Gambarani'], "Gary") 33 | 34 | assert collection[:] == [('Timothy', 'Crosley'), 35 | ('Timothy', 'Savannah'), 36 | ('Ryan', 'Scaife'), 37 | ('Gary', 'Cusimano'), 38 | ('Gary', 'Gambarani')] 39 | 40 | tims.append('Fritz') 41 | ryans.append('Frankhouser') 42 | assert collection[:] == [('Timothy', 'Crosley'), 43 | ('Timothy', 'Savannah'), 44 | ('Timothy', 'Fritz'), 45 | ('Ryan', 'Scaife'), 46 | ('Ryan', 'Frankhouser'), 47 | ('Gary', 'Cusimano'), 48 | ('Gary', 'Gambarani')] 49 | 50 | assert collection[1:6] == [('Timothy', 'Savannah'), 51 | ('Timothy', 'Fritz'), 52 | ('Ryan', 'Scaife'), 53 | ('Ryan', 'Frankhouser'), 54 | ('Gary', 'Cusimano')] 55 | 56 | assert collection.pop() == ('Gary', 'Gambarani') 57 | assert collection[:] == [('Timothy', 'Crosley'), 58 | ('Timothy', 'Savannah'), 59 | ('Timothy', 'Fritz'), 60 | ('Ryan', 'Scaife'), 61 | ('Ryan', 'Frankhouser'), 62 | ('Gary', 'Cusimano')] 63 | 64 | for first, last in collection: 65 | assert first and last 66 | 67 | looped = 0 68 | for first, last in collection.islice()[1:3]: 69 | assert first and last 70 | looped += 1 71 | 72 | assert looped == 2 73 | 74 | def test_sortedSet(): 75 | mySortedSet = SortedSet([4, 1, 5, 2, 3]) 76 | assert mySortedSet[:] == [4, 1, 5, 2, 3] 77 | mySortedSet.add(4) 78 | assert mySortedSet[:] == [4, 1, 5, 2, 3] 79 | mySortedSet.add(6) 80 | assert mySortedSet[:] == [4, 1, 5, 2, 3, 6] 81 | assert mySortedSet[0] == 4 82 | assert mySortedSet[1] == 1 83 | assert mySortedSet[2] == 5 84 | assert mySortedSet[3] == 2 85 | assert mySortedSet[4] == 3 86 | assert mySortedSet[5] == 6 87 | 88 | def test_Queryable(): 89 | queryable = Queryable(['a', 'b']) 90 | assert queryable.count() == 2 91 | assert queryable.__lowerStrings__('hi there') == 'hi there' 92 | assert queryable.__lowerStrings__('Hi There') == 'hi there' 93 | assert queryable.__lowerStrings__('HI THERE') == 'hi there' 94 | assert queryable.__lowerStrings__(u('hi there')) == u('hi there') 95 | assert queryable.__lowerStrings__(u('Hi There')) == u('hi there') 96 | assert queryable.__lowerStrings__(u('HI THERE')) == u('hi there') 97 | assert queryable.__lowerStrings__(57) == 57 98 | -------------------------------------------------------------------------------- /tests/test_layout.py: -------------------------------------------------------------------------------- 1 | ''' 2 | test_Layout.py 3 | 4 | Tests the functionality of thedom/Layout.py 5 | 6 | Copyright (C) 2015 Timothy Edmund Crosley 7 | 8 | This program is free software; you can redistribute it and/or 9 | modify it under the terms of the GNU General Public License 10 | as published by the Free Software Foundation; either version 2 11 | of the License, or (at your option) any later version. 12 | 13 | This program is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with this program; if not, write to the Free Software 20 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 21 | ''' 22 | 23 | from test_Base import ElementTester 24 | from thedom.All import Factory 25 | 26 | 27 | class TestStack(ElementTester): 28 | 29 | def setup_class(self): 30 | self.element = Factory.build("Stack", name="Test") 31 | self.element.add(Factory.build("Button", name="Button")) 32 | self.element.add(Factory.build("box", name="box")) 33 | 34 | 35 | class TestBox(ElementTester): 36 | 37 | def setup_class(self): 38 | self.element = Factory.build("Box", name="Test") 39 | 40 | 41 | class TestFlow(ElementTester): 42 | 43 | def setup_class(self): 44 | self.element = Factory.build("box", name="Test") 45 | self.flowContainer = self.element.add(Factory.build("flow", name="flow")) 46 | self.flowContainer.add(Factory.build("Button", name="Button")) 47 | self.flowContainer.add(Factory.build("box", name="box")) 48 | 49 | class TestHorizontal(ElementTester): 50 | 51 | def setup_class(self): 52 | self.element = Factory.build("Horizontal", name="Test") 53 | self.element.add(Factory.build("Button", name="Button")) 54 | self.element.add(Factory.build("Flow", name="Container")) 55 | 56 | 57 | class TestVertical(ElementTester): 58 | 59 | def setup_class(self): 60 | self.element = Factory.build("Vertical", name="Test") 61 | self.element.add(Factory.build("Button", name="Button")) 62 | self.element.add(Factory.build("Flow", name="Container")) 63 | 64 | 65 | class TestFields(ElementTester): 66 | 67 | def setup_class(self): 68 | self.element = Factory.build("Fields", name="Test") 69 | 70 | 71 | class TestLineBreak(ElementTester): 72 | 73 | def setup_class(self): 74 | self.element = Factory.build("lineBreak", name="Test") 75 | 76 | 77 | class TestHorizontalRule(ElementTester): 78 | 79 | def setup_class(self): 80 | self.element = Factory.build('HorizontalRule', 'test') 81 | 82 | 83 | class TestVerticalRule(ElementTester): 84 | 85 | def setup_class(self): 86 | self.element = Factory.build('VerticalRule', 'test') 87 | 88 | class TestCenter(ElementTester): 89 | 90 | def setup_class(self): 91 | self.element = Factory.build('center', 'test') 92 | -------------------------------------------------------------------------------- /tests/test_navigation.py: -------------------------------------------------------------------------------- 1 | ''' 2 | test_Navigation.py 3 | 4 | Tests the functionality of thedom/Navigation.py 5 | 6 | Copyright (C) 2015 Timothy Edmund Crosley 7 | 8 | This program is free software; you can redistribute it and/or 9 | modify it under the terms of the GNU General Public License 10 | as published by the Free Software Foundation; either version 2 11 | of the License, or (at your option) any later version. 12 | 13 | This program is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with this program; if not, write to the Free Software 20 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 21 | ''' 22 | 23 | from test_Base import ElementTester 24 | from thedom.All import Factory 25 | from thedom.Buttons import Link 26 | 27 | 28 | class TestItemPager(ElementTester): 29 | 30 | def setup_method(self, method): 31 | self.element = Factory.build('ItemPager', 'Test') 32 | self.element.itemsPerPage = 5 33 | self.element.pagesShownAtOnce = 15 34 | 35 | def test_noIetms(self): 36 | self.element.currentPageItems() == () 37 | 38 | def test_setItems(self): 39 | self.element.setItems(range(0, 100)) 40 | assert list(self.element.currentPageItems()) == list(range(0, 5)) 41 | self.test_validXML() 42 | 43 | self.element.insertVariables({'TestIndex':5}) 44 | self.element.setItems(range(0, 100)) 45 | assert list(self.element.currentPageItems()) == list(range(5, 10)) 46 | self.test_validXML() 47 | 48 | self.element.setItems(range(0, 4)) 49 | assert list(self.element.currentPageItems()) == list(range(0, 4)) 50 | self.test_validXML() 51 | assert self.element.nextButton.shown() == False 52 | assert self.element.lastButton.shown() == False 53 | 54 | self.element.setItems([]) 55 | self.test_validXML() 56 | assert self.element.shown() == False 57 | 58 | #Show all toggled 59 | self.element.showAllButton.toggle() 60 | self.element.setItems(range(0, 100)) 61 | assert list(self.element.currentPageItems()) == list(range(0, 100)) 62 | self.test_validXML() 63 | 64 | 65 | class TestJumpToLetter(ElementTester): 66 | 67 | def setup_class(self): 68 | self.element = Factory.build('JumpToLetter', 'test') 69 | -------------------------------------------------------------------------------- /tests/test_parser.py: -------------------------------------------------------------------------------- 1 | ''' 2 | test_Parser.py 3 | 4 | Tests the functionality of thedom/Parser.py 5 | 6 | Copyright (C) 2015 Timothy Edmund Crosley 7 | 8 | This program is free software; you can redistribute it and/or 9 | modify it under the terms of the GNU General Public License 10 | as published by the Free Software Foundation; either version 2 11 | of the License, or (at your option) any later version. 12 | 13 | This program is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with this program; if not, write to the Free Software 20 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 21 | ''' 22 | 23 | from thedom.Parser import NodeTree 24 | 25 | TREE = NodeTree("""
""") 26 | EXPECTED_FORMATTED_OUTPUT = """ 27 | 28 | 29 | 30 |
31 |
32 |
33 | 34 | """ 35 | 36 | def test_tree(): 37 | """ 38 | Test that NodeTree correctly produces a parse-able tree of elements 39 | """ 40 | TREE.toHTML() 41 | assert TREE[0]._tagName == "html" 42 | assert TREE[0].count() == 2 43 | assert TREE[0][0]._tagName == "head" 44 | assert TREE[0][1]._tagName == "body" 45 | 46 | body = TREE[0][1] 47 | assert body.count() == 2 48 | assert body[0]._tagName == "br" 49 | assert body[0]._tagSelfCloses == True 50 | assert body[1]._tagName == "div" 51 | assert body[1]._tagSelfCloses == False 52 | assert body[1].id == "myDiv" 53 | 54 | def test_condensedOutput(): 55 | """ 56 | Test that the Node tree correctly produces condensed html 57 | """ 58 | output = TREE.toHTML() 59 | assert output == '
' 60 | 61 | def test_formattedOutput(): 62 | """ 63 | Test that the Node tree correctly produces formatted (pretty) html 64 | """ 65 | output = TREE.toHTML(formatted=True) 66 | assert output == EXPECTED_FORMATTED_OUTPUT 67 | -------------------------------------------------------------------------------- /tests/test_position_controller.py: -------------------------------------------------------------------------------- 1 | ''' 2 | test_PositionController.py 3 | 4 | Tests the functionality of thedom/PositionController.py 5 | 6 | Copyright (C) 2015 Timothy Edmund Crosley 7 | 8 | This program is free software; you can redistribute it and/or 9 | modify it under the terms of the GNU General Public License 10 | as published by the Free Software Foundation; either version 2 11 | of the License, or (at your option) any later version. 12 | 13 | This program is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with this program; if not, write to the Free Software 20 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 21 | ''' 22 | 23 | from thedom.PositionController import PositionController 24 | 25 | 26 | class TestPositionController(object): 27 | 28 | def setup_method(self, object): 29 | """ 30 | Creates a position controller containing 50 strings, labeled Item0 - Item49 31 | """ 32 | self.testList = [] 33 | for count in range(50): 34 | self.testList.append("Item" + str(count)) 35 | 36 | self.positionController = PositionController(items=self.testList, itemsPerPage=5, 37 | pagesShownAtOnce=4) 38 | 39 | def test_attributes(self): 40 | """ 41 | Tests to ensure public attributes are set correctly post initialization 42 | """ 43 | assert self.positionController.pagesShownAtOnce == 4 44 | assert self.positionController.allItems == self.testList 45 | assert self.positionController.length == 50 46 | assert self.positionController.empty == False 47 | assert self.positionController.itemsPerPage == 5 48 | assert self.positionController.numberOfPages == 10 49 | 50 | assert self.positionController.startIndex == 0 51 | assert self.positionController.arePrev == False 52 | assert self.positionController.areMore == True 53 | assert self.positionController.page == 0 54 | assert self.positionController.pageNumber == 1 55 | assert self.positionController.currentPageItems == ['Item0', 'Item1', 'Item2', 'Item3', 56 | 'Item4'] 57 | 58 | def test_setIndex(self): 59 | """ 60 | Test to ensure changing the page index resets public attributes correctly 61 | """ 62 | self.positionController.setIndex(5) 63 | 64 | assert self.positionController.startIndex == 5 65 | assert self.positionController.arePrev == True 66 | assert self.positionController.areMore == True 67 | assert self.positionController.page == 1 68 | assert self.positionController.pageNumber == 2 69 | assert self.positionController.currentPageItems == ['Item5', 'Item6', 'Item7', 'Item8', 70 | 'Item9'] 71 | 72 | def test_nextPage(self): 73 | """ 74 | Test to ensure incrementing the page updates the positionController correctly 75 | """ 76 | self.positionController.nextPage() 77 | 78 | assert self.positionController.startIndex == 5 79 | assert self.positionController.arePrev == True 80 | assert self.positionController.areMore == True 81 | assert self.positionController.page == 1 82 | assert self.positionController.pageNumber == 2 83 | assert self.positionController.currentPageItems == ['Item5', 'Item6', 'Item7', 'Item8', 84 | 'Item9'] 85 | 86 | def test_prevPage(self): 87 | """ 88 | Test to ensure deincrementing the page updates the positionController correctly 89 | """ 90 | self.positionController.nextPage() 91 | self.positionController.prevPage() 92 | 93 | assert self.positionController.startIndex == 0 94 | assert self.positionController.arePrev == False 95 | assert self.positionController.areMore == True 96 | assert self.positionController.page == 0 97 | assert self.positionController.pageNumber == 1 98 | assert self.positionController.currentPageItems == ['Item0', 'Item1', 'Item2', 'Item3', 99 | 'Item4'] 100 | 101 | def test_setPage(self): 102 | """ 103 | Test to ensure setting the page updates the positionController correctly 104 | """ 105 | 106 | self.positionController.setPage(3) 107 | assert self.positionController.startIndex == 15 108 | assert self.positionController.arePrev == True 109 | assert self.positionController.areMore == True 110 | assert self.positionController.page == 3 111 | assert self.positionController.pageNumber == 4 112 | assert self.positionController.currentPageItems == ['Item15', 'Item16', 'Item17', 'Item18', 113 | 'Item19'] 114 | 115 | def test_pageIndex(self): 116 | """ 117 | Test to ensure page index correctly maps up to page number 118 | """ 119 | assert self.positionController.pageIndex(0) == 0 120 | assert self.positionController.pageIndex(1) == 5 121 | assert self.positionController.pageIndex(2) == 10 122 | assert self.positionController.pageIndex(3) == 15 123 | assert self.positionController.pageIndex(4) == 20 124 | 125 | def test_pageList(self): 126 | """ 127 | Test to ensure pageList method correctly returns page indexes 128 | """ 129 | pageList = self.positionController.pageList() 130 | assert len(pageList) == 4 131 | assert pageList == [0, 5, 10, 15] 132 | -------------------------------------------------------------------------------- /tests/test_printing.py: -------------------------------------------------------------------------------- 1 | ''' 2 | test_Printing.py 3 | 4 | Tests the functionality of thedom/Printing.py 5 | 6 | Copyright (C) 2015 Timothy Edmund Crosley 7 | 8 | This program is free software; you can redistribute it and/or 9 | modify it under the terms of the GNU General Public License 10 | as published by the Free Software Foundation; either version 2 11 | of the License, or (at your option) any later version. 12 | 13 | This program is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with this program; if not, write to the Free Software 20 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 21 | ''' 22 | 23 | from test_Base import ElementTester 24 | from thedom.All import Factory 25 | 26 | 27 | class TestPageBreak(ElementTester): 28 | 29 | def setup_method(self, method): 30 | self.element = Factory.build('pagebreak', 'test') 31 | 32 | 33 | class TestUnPrintable(ElementTester): 34 | 35 | def setup_method(self, method): 36 | self.element = Factory.build('unprintable', 'test') 37 | -------------------------------------------------------------------------------- /tests/test_resources.py: -------------------------------------------------------------------------------- 1 | ''' 2 | test_Resources.py 3 | 4 | Tests the functionality of thedom/Resources.py 5 | 6 | Copyright (C) 2015 Timothy Edmund Crosley 7 | 8 | This program is free software; you can redistribute it and/or 9 | modify it under the terms of the GNU General Public License 10 | as published by the Free Software Foundation; either version 2 11 | of the License, or (at your option) any later version. 12 | 13 | This program is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with this program; if not, write to the Free Software 20 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 21 | ''' 22 | 23 | from test_Base import ElementTester 24 | from thedom.All import Factory 25 | from thedom.Resources import ScriptContainer 26 | 27 | 28 | class TestResourceFile(ElementTester): 29 | 30 | def setup_class(self): 31 | self.element = Factory.build("ResourceFile", "Test") 32 | self.element.setFile("javascript.js") 33 | 34 | def test_setFile(self): 35 | self.element.setFile("") 36 | assert self.element.resourceType == None 37 | assert self.element.fileName == "" 38 | 39 | self.element.setFile("Style.css") 40 | assert self.element.resourceType == "css" 41 | assert self.element.fileName == "Style.css" 42 | 43 | self.element.setFile("Javascript.js") 44 | assert self.element.resourceType == "javascript" 45 | assert self.element.fileName == "Javascript.js" 46 | 47 | 48 | class TestScriptContainer(ElementTester): 49 | 50 | def setup_method(self, method): 51 | self.element = ScriptContainer() 52 | 53 | def test_addScript(self): 54 | assert self.element._scripts == [] 55 | 56 | self.element.addScript("alert('I am a script :D');") 57 | self.element.addScript("var value = 'I am another script';") 58 | assert self.element._scripts == ["alert('I am a script :D');", 59 | "var value = 'I am another script';"] 60 | assert "alert('I am a script :D');" in self.element.toHTML() 61 | assert "var value = 'I am another script';" in self.element.toHTML() 62 | 63 | def test_removeScript(self): 64 | assert self.element._scripts == [] 65 | 66 | self.element.addScript("alert('I am a script :D');") 67 | assert self.element._scripts == ["alert('I am a script :D');"] 68 | 69 | self.element.removeScript("alert('I am a script :D');") 70 | assert self.element._scripts == [] 71 | -------------------------------------------------------------------------------- /tests/test_social.py: -------------------------------------------------------------------------------- 1 | ''' 2 | test_Social.py 3 | 4 | Tests the functionality of thedom/Social.py 5 | 6 | Copyright (C) 2015 Timothy Edmund Crosley 7 | 8 | This program is free software; you can redistribute it and/or 9 | modify it under the terms of the GNU General Public License 10 | as published by the Free Software Foundation; either version 2 11 | of the License, or (at your option) any later version. 12 | 13 | This program is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with this program; if not, write to the Free Software 20 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 21 | ''' 22 | 23 | from test_Base import ElementTester 24 | from thedom.All import Factory 25 | 26 | 27 | class TestTwitterBadge(ElementTester): 28 | 29 | def setup_method(self, element): 30 | self.element = Factory.build("TwitterBadge", name="Test") 31 | 32 | def _parseElement(self, element): 33 | return True # Contains xml our parser does not see as valid but cant be changed due to external API 34 | 35 | 36 | class TestGooglePlusBadge(ElementTester): 37 | 38 | def setup_method(self, element): 39 | self.element = Factory.build("GooglePlusBadge", name="Test") 40 | 41 | 42 | class TestFacebookLike(ElementTester): 43 | 44 | def setup_method(self, element): 45 | self.element = Factory.build("FacebookLike", name="Test") 46 | 47 | def _parseElement(self, element): 48 | return True # Contains xml our parser does not see as valid but cant be changed due to external API 49 | 50 | 51 | class TestFacebookAPI(ElementTester): 52 | 53 | def setup_method(self, element): 54 | self.element = Factory.build("FacebookAPI", name="Test") 55 | 56 | def _parseElement(self, element): 57 | return True # Contains xml our parser does not see as valid but cant be changed due to external API 58 | 59 | 60 | class TestGravatar(ElementTester): 61 | 62 | def setup_method(self, element): 63 | self.element = Factory.build("Gravatar", name="Test") 64 | self.element.email = "timothy.crosley@gmail.com" 65 | -------------------------------------------------------------------------------- /tests/test_string_utils.py: -------------------------------------------------------------------------------- 1 | ''' 2 | test_StringUtils.py 3 | 4 | Tests the functionality of thedom/StringUtils.py 5 | 6 | Copyright (C) 2015 Timothy Edmund Crosley 7 | 8 | This program is free software; you can redistribute it and/or 9 | modify it under the terms of the GNU General Public License 10 | as published by the Free Software Foundation; either version 2 11 | of the License, or (at your option) any later version. 12 | 13 | This program is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with this program; if not, write to the Free Software 20 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 21 | ''' 22 | 23 | from thedom import StringUtils 24 | from thedom.MultiplePythonSupport import * 25 | 26 | 27 | def test_removeAlphas(): 28 | """ Ensure that the utility function to remove alpha characters works successfully """ 29 | assert StringUtils.removeAlphas('afjsafdl121323213adfas1231321') == "1213232131231321" 30 | assert StringUtils.removeAlphas('213123123123231') == "213123123123231" 31 | 32 | def test_interpretFromString(): 33 | """Ensure the interpret from string utility method correctly takes strings and turns them into 34 | objects 35 | """ 36 | assert StringUtils.interpretFromString("True") == True 37 | assert StringUtils.interpretFromString("trUE") == True 38 | 39 | assert StringUtils.interpretFromString("False") == False 40 | assert StringUtils.interpretFromString("fAlSe") == False 41 | 42 | assert StringUtils.interpretFromString("None") == None 43 | assert StringUtils.interpretFromString("NOnE") == None 44 | 45 | assert StringUtils.interpretFromString("Some other value") == "Some other value" 46 | 47 | def test_stripControlChars(): 48 | """Ensure we properly remove control characters (not including \r\n\t""" 49 | controlCharText = ''.join(StringUtils.INVALID_CONTROL_CHARACTERS) + \ 50 | "THE STRING" + ''.join(StringUtils.INVALID_CONTROL_CHARACTERS) 51 | assert StringUtils.stripControlChars(controlCharText) == "THE STRING" 52 | assert StringUtils.stripControlChars(controlCharText, fromFront=False) == \ 53 | ''.join(StringUtils.INVALID_CONTROL_CHARACTERS) + "THE STRING" 54 | assert StringUtils.stripControlChars(controlCharText, fromBack=False) == \ 55 | "THE STRING" + ''.join(StringUtils.INVALID_CONTROL_CHARACTERS) 56 | assert StringUtils.stripControlChars(controlCharText, fromFront=False, fromBack=False) == \ 57 | ''.join(StringUtils.INVALID_CONTROL_CHARACTERS) + "THE STRING" + \ 58 | ''.join(StringUtils.INVALID_CONTROL_CHARACTERS) 59 | 60 | def test_listReplace(): 61 | """Ensure replacing a list of items from a string works correctly""" 62 | myString = "A string of text" 63 | assert StringUtils.listReplace(myString, ['A', 'text', 'string'], "AHH") == "AHH AHH of AHH" 64 | assert StringUtils.listReplace(myString, ['A', 'text', 'string'], ["1", "2", "3"]) == "1 3 of 2" 65 | 66 | def test_removeDelimiters(): 67 | """Ensure removing delimeters works correctly""" 68 | string = "Th.is, shou+ld wo-rk /I thi\\nk" 69 | assert StringUtils.removeDelimiters(string) == "This should work I think" 70 | assert StringUtils.removeDelimiters(string, replacement=" ") == "Th is shou ld wo rk I thi nk" 71 | 72 | def test_findIndexes(): 73 | string = "There is an A here and an A here there is an A everywhere" 74 | assert StringUtils.findIndexes(string, "A") == set((12, 26, 45)) 75 | assert StringUtils.findIndexes(string, "is an") == set((6, 39)) 76 | assert StringUtils.findIndexes(string, "monkey") == set() 77 | 78 | def test_generateRandomKey(): 79 | randomKey1 = StringUtils.generateRandomKey() 80 | randomKey2 = StringUtils.generateRandomKey() 81 | assert randomKey1 != randomKey2 82 | assert len(randomKey1) == len(randomKey2) == 20 83 | 84 | randomKey1 = StringUtils.generateRandomKey(40) 85 | randomKey2 = StringUtils.generateRandomKey(40) 86 | assert randomKey1 != randomKey2 87 | assert len(randomKey1) == len(randomKey2) == 40 88 | -------------------------------------------------------------------------------- /tests/test_template.shpaml: -------------------------------------------------------------------------------- 1 | container randomattribute=Hello 2 | childelement#SomeRandomId name=SomeRandomName 3 | > childishchildelement -------------------------------------------------------------------------------- /tests/test_template.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /tests/test_ui_template.py: -------------------------------------------------------------------------------- 1 | ''' 2 | test_UITemplate.py 3 | 4 | Tests the functionality of thedom/UITemplate.py 5 | 6 | Copyright (C) 2015 Timothy Edmund Crosley 7 | 8 | This program is free software; you can redistribute it and/or 9 | modify it under the terms of the GNU General Public License 10 | as published by the Free Software Foundation; either version 2 11 | of the License, or (at your option) any later version. 12 | 13 | This program is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with this program; if not, write to the Free Software 20 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 21 | ''' 22 | 23 | from thedom import UITemplate 24 | 25 | EXPECTED_STRUCTURE = UITemplate.Template('container', properties=(("randomattribute", "Hello"),), 26 | childElements=( 27 | UITemplate.Template('childelement', id="SomeRandomId", name="SomeRandomName", 28 | childElements=( 29 | UITemplate.Template("childishchildelement"), 30 | )),)) 31 | 32 | def test_fromFile(): 33 | """ 34 | Ensure UITemplate creates a dictionary structure from an XML File correctly 35 | """ 36 | assert UITemplate.fromFile("testTemplate.xml", formatType=UITemplate.XML) == EXPECTED_STRUCTURE #xml file 37 | assert UITemplate.fromFile("testTemplate.shpaml", formatType=UITemplate.SHPAML) == EXPECTED_STRUCTURE #shpaml file 38 | 39 | def test_fromXML(): 40 | """ 41 | Ensure UITemplate creates a dictionary structure from XML correctly 42 | """ 43 | xml = """ 44 | 45 | 46 | 47 | """ 48 | 49 | assert UITemplate.fromXML(xml) == EXPECTED_STRUCTURE 50 | 51 | def test_fromSHPAML(): 52 | """ 53 | Ensure UITemplate creates a dictionary structure from SHPAML correctly 54 | """ 55 | shpmal = """ 56 | container randomattribute=Hello 57 | childelement#SomeRandomId name=SomeRandomName 58 | > childishchildelement 59 | """ 60 | 61 | assert UITemplate.fromSHPAML(shpmal) == EXPECTED_STRUCTURE 62 | -------------------------------------------------------------------------------- /tests/tests_benchmark.py: -------------------------------------------------------------------------------- 1 | ''' 2 | test_Benchmark.py 3 | 4 | Tests the results of benchmark_thedom.py against project performance metrics 5 | 6 | Copyright (C) 2015 Timothy Edmund Crosley 7 | 8 | This program is free software; you can redistribute it and/or 9 | modify it under the terms of the GNU General Public License 10 | as published by the Free Software Foundation; either version 2 11 | of the License, or (at your option) any later version. 12 | 13 | This program is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with this program; if not, write to the Free Software 20 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 21 | ''' 22 | 23 | try: 24 | import cPickle as pickle 25 | except ImportError: 26 | import pickle 27 | 28 | import os 29 | import subprocess 30 | import sys 31 | 32 | 33 | class Testthedom_Benchmark(object): 34 | 35 | def run_benchmark(self): 36 | subprocess.Popen("python benchmark_thedom.py", shell=True).wait() 37 | with open(".test_thedom_Benchmark.results") as resultFile: 38 | if sys.version >= "3": 39 | results = pickle.loads(bytes(resultFile.read(), 'utf8')) 40 | else: 41 | results = pickle.loads(resultFile.read()) 42 | os.remove(".test_thedom_Benchmark.results") 43 | return results 44 | 45 | def test_benchmark(self): 46 | results = self.run_benchmark() 47 | assert(results['loopedCreate'] < 20.0) 48 | assert(results['longestCreationTime'] < 0.010) 49 | assert(results['createAllOnce'] < 0.250) 50 | -------------------------------------------------------------------------------- /thedom/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | thedom/__init__.py 3 | 4 | thedom is a library that provides classes to represent sections of a page. 5 | At the most basic point a Node is a 1:1 mapping to a DOM object, however 6 | as thedom can each contain child elements - a single Node from an 7 | API usage point of view can define any concept or widget on a web page no matter 8 | how complex. 9 | 10 | Copyright (C) 2015 Timothy Edmund Crosley 11 | 12 | This program is free software; you can redistribute it and/or 13 | modify it under the terms of the GNU General Public License 14 | as published by the Free Software Foundation; either version 2 15 | of the License, or (at your option) any later version. 16 | 17 | This program is distributed in the hope that it will be useful, 18 | but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | GNU General Public License for more details. 21 | 22 | You should have received a copy of the GNU General Public License 23 | along with this program; if not, write to the Free Software 24 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 25 | """ 26 | 27 | __version__ = "0.0.1" 28 | -------------------------------------------------------------------------------- /thedom/all.py: -------------------------------------------------------------------------------- 1 | ''' 2 | All.py 3 | 4 | A Factory that contains all the base thedom, additionally can be used to import all thedom with 5 | a single import (for example: import thedom.All as thedom; layout = thedom.Layout.Vertical()) 6 | 7 | Copyright (C) 2015 Timothy Edmund Crosley 8 | 9 | This program is free software; you can redistribute it and/or 10 | modify it under the terms of the GNU General Public License 11 | as published by the Free Software Foundation; either version 2 12 | of the License, or (at your option) any later version. 13 | 14 | This program is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty 16 | , of 17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | GNU General Public License for more details. 19 | 20 | You should have received a copy of the GNU General Public License 21 | along with this program; if not, write to the Free Software 22 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 23 | ''' 24 | 25 | from . import (DOM, Base, Buttons, Charts, CodeDocumentation, Containers, DataViews, Display, Document, Factory, 26 | HiddenInputs, Inputs, Layout, Navigation, Printing, Resources, Social, UITemplate, Validators) 27 | 28 | FactoryClasses = Factory 29 | Factory = Factory.Composite((Validators.Factory, DOM.Factory, Buttons.Factory, DataViews.Factory, 30 | Display.Factory, HiddenInputs.Factory, Inputs.Factory, Layout.Factory, 31 | Navigation.Factory, Resources.Factory, Containers.Factory, Charts.Factory, 32 | Printing.Factory, Document.Factory, CodeDocumentation.Factory, Social.Factory)) 33 | -------------------------------------------------------------------------------- /thedom/charts.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Charts.py 3 | 4 | Contains elements that utilize the google charts public API to produce charts based on common data 5 | 6 | Copyright (C) 2015 Timothy Edmund Crosley 7 | 8 | This program is free software; you can redistribute it and/or 9 | modify it under the terms of the GNU General Public License 10 | as published by the Free Software Foundation; either version 2 11 | of the License, or (at your option) any later version. 12 | 13 | This program is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with this program; if not, write to the Free Software 20 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 21 | ''' 22 | 23 | from . import Base, Factory 24 | from .Display import Image 25 | from .MultiplePythonSupport import * 26 | 27 | Factory = Factory.Factory("Charts") 28 | 29 | 30 | class GoogleChart(Image): 31 | """ 32 | Provides a way for the google chart api to be used via thedom 33 | """ 34 | __slots__ = ('__dataPoints__', '__height__', '__width__') 35 | chartType = None 36 | url = ('http://chart.apis.google.com/chart?cht=' 37 | '%(chart)s&chs=%(width)sx%(height)s&chd=t:%(data)s&chl=%(labels)s&chbh=a' 38 | '&chxt=y&chds=0,%(max)f&chxr=0,0,%(max)f&chco=4D89F9') 39 | properties = Image.properties.copy() 40 | properties['height'] = {'action':'setHeight', 'type':'int'} 41 | properties['width'] = {'action':'setWidth', 'type':'int'} 42 | 43 | def _create(self, id=None, name=None, parent=None, **kwargs): 44 | Image._create(self, id, name, parent) 45 | self.__dataPoints__ = {} 46 | self.__height__ = 100 47 | self.__width__ = 100 48 | 49 | def setWidth(self, width): 50 | """ 51 | Set the width of the google chart in pixels (maximum allowed by google is 1000 pixels) 52 | """ 53 | width = int(width) 54 | if width > 1000: 55 | raise ValueError("Google charts has a maximum width limit of 1000 pixels") 56 | self.__width__ = width 57 | 58 | def width(self): 59 | """ 60 | Returns the set width of the google chart in pixels 61 | """ 62 | return self.__width__ 63 | 64 | def setHeight(self, height): 65 | """ 66 | Set the height of the google chart in pixels (maximum allowed by google is 1000 pixels) 67 | """ 68 | height = int(height) 69 | if height > 1000: 70 | raise ValueError("Google charts has a maximum height limit of 1000 pixels") 71 | self.__height__ = height 72 | 73 | def height(self): 74 | """ 75 | Returns the set height of the google chart in pixels 76 | """ 77 | return self.__height__ 78 | 79 | def addData(self, label, value): 80 | """ 81 | Adds a data point to the chart, 82 | label: the label to associate with the data point 83 | value: the numeric value of the data 84 | """ 85 | self.__dataPoints__[str(label)] = value 86 | 87 | def _render(self): 88 | Image._render(self) 89 | 90 | # Update Source data 91 | data = self.__dataPoints__ or {'No Data!':'100'} 92 | self.style['width'] = self.__width__ 93 | 94 | items = [] 95 | for key, value in iteritems(data): 96 | items.append((value, key)) 97 | items.sort() 98 | keys = [key for value, key in items] 99 | values = [value for value, key in items] 100 | 101 | self.style['height'] = self.__height__ 102 | self.setProperty('src', self.getURL(keys, values)) 103 | 104 | def getURL(self, keys, values): 105 | """ 106 | Returns the google chart url based on the data points given 107 | """ 108 | return self.url % {'chart':self.chartType, 'width':str(self.__width__), 109 | 'height':str(self.__height__), 110 | 'data':",".join([str(value) for value in values]), 111 | 'labels':"|".join(keys), 112 | 'max':float(max(values)), 113 | } 114 | 115 | 116 | class PieChart(GoogleChart): 117 | """ 118 | Implementation of Google's pie chart 119 | """ 120 | __slots__ = () 121 | chartType = "p" 122 | 123 | Factory.addProduct(PieChart) 124 | 125 | 126 | class PieChart3D(GoogleChart): 127 | """ 128 | Implementation of Google's 3d pie chart 129 | """ 130 | __slots__ = () 131 | chartType = "p3" 132 | 133 | Factory.addProduct(PieChart3D) 134 | 135 | 136 | class HorizontalBarChart(GoogleChart): 137 | """ 138 | Implementation of Googles Horizontal Bar Chart 139 | """ 140 | __slots__ = () 141 | chartType = "bhs" 142 | url = ('http://chart.apis.google.com/chart?cht=' 143 | '%(chart)s&chs=%(width)sx%(height)s&chd=t:%(data)s&chxl=1:|%(labels)s&chbh=a' 144 | '&chxt=x,y&chds=0,%(max)f&chxr=0,0,%(max)f&chco=4D89F9') 145 | 146 | def getURL(self, keys, values): 147 | """ 148 | Returns the google chart url based on the data points given 149 | """ 150 | return self.url % {'chart':self.chartType, 'width':str(self.__width__), 151 | 'height':str(self.__height__), 152 | 'data':",".join([str(value) for value in values]), 153 | 'labels':"|".join(reversed(keys)), 154 | 'max':float(max(values)), 155 | } 156 | 157 | Factory.addProduct(HorizontalBarChart) 158 | 159 | 160 | class VerticalBarChart(GoogleChart): 161 | """ 162 | Implementation of Google's Vertical Bar Chart 163 | """ 164 | __slots__ = () 165 | chartType = "bvs" 166 | 167 | Factory.addProduct(VerticalBarChart) 168 | 169 | 170 | class LineChart(GoogleChart): 171 | """ 172 | Implementation of Google's Line Chart 173 | """ 174 | __slots__ = () 175 | chartType = "lc" 176 | 177 | Factory.addProduct(LineChart) 178 | -------------------------------------------------------------------------------- /thedom/code_documentation.py: -------------------------------------------------------------------------------- 1 | ''' 2 | CodeDocumentation.py 3 | 4 | Contains elements that are used for displaying and documenting code 5 | uses pygments to enable code highlighting 6 | 7 | Copyright (C) 2015 Timothy Edmund Crosley 8 | 9 | This program is free software; you can redistribute it and/or 10 | modify it under the terms of the GNU General Public License 11 | as published by the Free Software Foundation; either version 2 12 | of the License, or (at your option) any later version. 13 | 14 | This program is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with this program; if not, write to the Free Software 21 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 22 | ''' 23 | 24 | try: 25 | hasPygments = True 26 | from pygments import highlight 27 | from pygments.formatters import HtmlFormatter 28 | from pygments.lexers import get_lexer_by_name, get_lexer_for_filename 29 | except ImportError: 30 | hasPygments = False 31 | 32 | from . import DOM, Base, DictUtils, Factory 33 | from .Inputs import ValueElement 34 | from .MethodUtils import CallBack 35 | from .MultiplePythonSupport import * 36 | 37 | Factory = Factory.Factory("CodeDocumentation") 38 | 39 | 40 | class CodeSnippet(DOM.Pre): 41 | """ 42 | Enables adding a snippet of code directly to a page. 43 | """ 44 | __slots__ = ('code', 'lexer', 'showLineNumbers', '_textNode') 45 | tagName = "pre" 46 | properties = Base.Node.properties.copy() 47 | properties['code'] = {'action':'classAttribute'} 48 | properties['lexer'] = {'action':'classAttribute'} 49 | properties['showLineNumbers'] = {'action':'classAttribute', 'type':'bool'} 50 | 51 | def _create(self, id=None, name=None, parent=None, **kwargs): 52 | DOM.Pre._create(self, id, name, parent, **kwargs) 53 | 54 | self._textNode = self.add(Base.TextNode()) 55 | 56 | self.showLineNumbers = False 57 | self.lexer = "python" 58 | self.code = "" 59 | 60 | def _getCode(self): 61 | return self.code.replace("\\n", "\n") 62 | 63 | def _getLexer(self): 64 | return get_lexer_by_name(self.lexer) 65 | 66 | def _render(self): 67 | """ 68 | Renders the code with pygments if it is available otherwise with a simple pre-tag 69 | """ 70 | DOM.Pre._render(self) 71 | if not hasPygments: 72 | self._textNode.setText(self.code) 73 | return 74 | 75 | self._tagName = "span" 76 | formatter = HtmlFormatter(linenos=self.showLineNumbers) 77 | self._textNode.setText(highlight(self._getCode(), self._getLexer(), formatter)) 78 | 79 | Factory.addProduct(CodeSnippet) 80 | 81 | 82 | class SourceFile(CodeSnippet): 83 | """ 84 | Enables adding a formatted source file directly to a page. 85 | """ 86 | 87 | def _getCode(self): 88 | if self.code: 89 | with open(self.code, "r") as openFile: 90 | return openFile.read() 91 | return "" 92 | 93 | def _getLexer(self): 94 | if self.lexer: 95 | return get_lexer_by_name(self.lexer) 96 | return get_lexer_for_filename(self.code) 97 | 98 | Factory.addProduct(SourceFile) 99 | -------------------------------------------------------------------------------- /thedom/common_javascript.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timothycrosley/thedom/73833110a298456a1f24ab8e4f5cec09a0655c2d/thedom/common_javascript.js -------------------------------------------------------------------------------- /thedom/compile.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Compile.py 3 | 4 | Provides utilities for taking Node Template files and turning them into optimized python code. 5 | 6 | Classes and methods that aid in creating python dictionaries from XML or SHPAML templates 7 | 8 | Copyright (C) 2015 Timothy Edmund Crosley 9 | 10 | This program is free software; you can redistribute it and/or 11 | modify it under the terms of the GNU General Public License 12 | as published by the Free Software Foundation; either version 2 13 | of the License, or (at your option) any later version. 14 | 15 | This program is distributed in the hope that it will be useful, 16 | but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | GNU General Public License for more details. 19 | 20 | You should have received a copy of the GNU General Public License 21 | along with this program; if not, write to the Free Software 22 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 23 | ''' 24 | 25 | from .All import Factory 26 | from .MultiplePythonSupport import * 27 | from .Types import StyleDict 28 | 29 | INDENT = " " 30 | SCRIPT_TEMPLATE = """# WARNING: DON'T EDIT AUTO-GENERATED 31 | 32 | from thedom.Base import Node, TextNode 33 | from thedom.Display import CacheElement, StraightHTML 34 | 35 | elementsExpanded = False 36 | %(cacheElements)s 37 | %(staticElements)s 38 | 39 | 40 | class Template(Node): 41 | __slots__ = %(accessors)s 42 | 43 | 44 | def build(factory): 45 | template = Template() 46 | 47 | global elementsExpanded 48 | if not elementsExpanded: 49 | products = factory.products 50 | %(defineElements)s 51 | elementsExpanded = True 52 | %(buildTemplate)s 53 | 54 | return template""" 55 | 56 | 57 | class CompiledTemplate(object): 58 | """ 59 | Represents a template that has been compiled and exec'd in the current runtime, produces a new Node 60 | representation of the template every time you call you call 'build()' behaving in a similar fashion as 61 | templates that have been compiled and saved into python modules. 62 | """ 63 | __slots__ = ('execNamespace', 'factory') 64 | def __init__(self, execNamespace, factory): 65 | self.execNamespace = execNamespace 66 | self.factory = factory 67 | 68 | def build(self, factory=None): 69 | """ 70 | Returns a Node representation of the template using the specified factory. 71 | """ 72 | factory = factory or self.factory 73 | return self.execNamespace['build'](factory) 74 | 75 | @classmethod 76 | def create(self, template, factory=Factory): 77 | """ 78 | Compiles a template in the current python runtime into optimized python bytecode using compile and exec 79 | returns a CompiledTemplate instance. 80 | """ 81 | code = compile(toPython(template, Factory), '', 'exec') 82 | nameSpace = {} 83 | exec(code, nameSpace) 84 | return CompiledTemplate(nameSpace, Factory) 85 | 86 | def toPython(template, factory=Factory): 87 | """ 88 | Takes a UITemplate.Template() and a factory, and returns python code that will generate the expected 89 | Node structure. 90 | """ 91 | return __createPythonFromTemplate(template, factory) 92 | 93 | def __createPythonFromTemplate(template, factory=None, parentNode=None, instance=0, elementsUsed=None, 94 | accessorsUsed=None, cacheElements=None, staticElements=None, indent=1): 95 | python = "" 96 | if elementsUsed is None: 97 | elementsUsed = set() 98 | if accessorsUsed is None: 99 | accessorsUsed = set() 100 | if cacheElements is None: 101 | cacheElements = set() 102 | if staticElements is None: 103 | staticElements = set() 104 | if not parentNode: 105 | parentNode = "template" 106 | 107 | indented = INDENT * indent 108 | newNode = "element" + str(instance) 109 | instance += 1 110 | if type(template) in (str, unicode): 111 | if not template: 112 | instance -= 1 113 | return ("", instance) 114 | python += "\n%s%s = %s.add(" % (indented, newNode, parentNode) 115 | python += 'TextNode("' + template + '"), ensureUnique=False)' 116 | return (python, instance) 117 | 118 | (accessor, id, name, create, properties, children) = (template.accessor, template.id, template.name, 119 | template.create, template.properties, template.childElements) 120 | elementsUsed.add(create) 121 | element = factory.products[create] 122 | create = create.replace("-", "_") 123 | if create in ("and", "or", "with", "if", "del", "template"): 124 | create = "_" + create 125 | 126 | accessor = accessor or id 127 | if accessor: 128 | accessor = accessor.replace("-", "_") 129 | 130 | isCached = False 131 | if create.endswith("cacheelement"): 132 | cacheElements.add(newNode) 133 | isCached = True 134 | python += "\n%s%s = globals()['%s']" % (indented, newNode, newNode) 135 | python += "\n%sif not %s.rendered():" % (indented, newNode) 136 | python += "\n%s%s = CacheElement()" % (indented + INDENT, newNode) 137 | python += "\n%sglobals()['%s'] = %s" % (indented + INDENT, newNode, newNode) 138 | elif create in ("static", "display-static") and parentNode != "template": 139 | html = CompiledTemplate.create(template, factory).build(factory).toHTML() 140 | staticElements.add('%s = StraightHTML(html="""%s""")' % (newNode, html)) 141 | python += "\n%s%s.add(%s, ensureUnique=False)" % (indented, parentNode, newNode) 142 | return (python, instance) 143 | else: 144 | python += '\n%s%s = %s(id=%s, name=%s, parent=%s)' % (indented, newNode, create.lower(), repr(id), 145 | repr(name), parentNode) 146 | for name, value in properties: 147 | if value is not None and name in element.properties: 148 | propertyDict = element.properties[name] 149 | propertyActions = propertyDict['action'].split('.') 150 | propertyAction = propertyActions.pop(-1) 151 | if propertyActions: 152 | propertyActions = "." + ".".join(propertyActions) 153 | else: 154 | propertyActions = "" 155 | propertyName = propertyDict.get('name', name) 156 | 157 | if propertyAction == "classAttribute": 158 | python += "\n%s%s%s.%s = %s" % (indented, newNode, propertyActions, propertyName, repr(value)) 159 | elif propertyAction == "attribute": 160 | python += "\n%s%s%s.attributes[%s] = %s" % (indented, newNode, propertyActions, repr(propertyName), 161 | repr(value)) 162 | elif propertyAction == "javascriptEvent": 163 | python += "\n%s%s%s.addJavascriptEvent(%s, %s)" % (indented, newNode, propertyActions, 164 | repr(propertyName), repr(value)) 165 | elif propertyAction == "call": 166 | if value: 167 | python += "\n%s%s%s.%s()" % (indented, newNode, propertyActions, propertyName) 168 | elif propertyAction == "send": 169 | python += "\n%s%s%s.%s(%s, %s)" % (indented, newNode, propertyActions, 170 | propertyName, repr(name), repr(value)) 171 | elif propertyAction == "addClassesFromString": 172 | python += "\n%s%s%s.addClasses(%s)" % (indented, newNode, propertyActions, 173 | repr(tuple(value.split(" ")))) 174 | elif propertyAction == "setStyleFromString": 175 | python += "\n%s%s%s.style.update(%s)" % (indented, newNode, propertyActions, 176 | repr(StyleDict.fromString(value))) 177 | else: 178 | python += "\n%s%s%s.%s(%s)" % (indented, newNode, propertyActions, propertyAction, repr(value)) 179 | 180 | if accessor: 181 | accessorsUsed.add(accessor) 182 | python += "\n%stemplate.%s = %s" % (indented, accessor, newNode) 183 | 184 | if children: 185 | if isCached: 186 | childAccessors = set() 187 | childIndent = indent + 1 188 | else: 189 | childAccessors = accessorsUsed 190 | childIndent = indent 191 | for node in children: 192 | (childPython, instance) = __createPythonFromTemplate(node, factory, newNode, instance, elementsUsed, 193 | childAccessors, cacheElements, staticElements, childIndent) 194 | python += childPython 195 | if isCached: 196 | if childAccessors: 197 | accessorsUsed.update(childAccessors) 198 | python += "\n%selse:" % indented 199 | for accessor in childAccessors: 200 | python += "\n%stemplate.%s = %s" % (indented + INDENT, accessor, newNode) 201 | 202 | 203 | python += "\n%s%s.add(%s, ensureUnique=False)" % (indented, parentNode, newNode) 204 | if parentNode == "template": 205 | defineElements = "" 206 | for elementName in elementsUsed: 207 | variableName = elementName.replace("-", "_") 208 | if variableName in ("and", "or", "with", "if", "del", "template"): 209 | variableName = "_" + variableName 210 | defineElements += "globals()['%s'] = products['%s']\n%s" % (variableName, elementName, INDENT * 2) 211 | 212 | cacheDefinitions = "" 213 | for elementName in cacheElements: 214 | cacheDefinitions += "%s = CacheElement()\n" % elementName 215 | 216 | return SCRIPT_TEMPLATE % {'accessors':tuple(accessorsUsed), 'buildTemplate':python, 217 | 'defineElements':defineElements, 'cacheElements':cacheDefinitions, 218 | 'staticElements':"\n".join(staticElements)} 219 | 220 | 221 | return (python, instance) 222 | -------------------------------------------------------------------------------- /thedom/connectable.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Connectable.py 3 | 4 | Connectable enables child object to create dynamic connections 5 | (via signals/slots) at run-time. Inspired by QT's signal / slot mechanism 6 | 7 | Copyright (C) 2015 Timothy Edmund Crosley 8 | 9 | This program is free software; you can redistribute it and/or 10 | modify it under the terms of the GNU General Public License 11 | as published by the Free Software Foundation; either version 2 12 | of the License, or (at your option) any later version. 13 | 14 | This program is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with this program; if not, write to the Free Software 21 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 22 | ''' 23 | 24 | from . import MethodUtils 25 | from .MultiplePythonSupport import * 26 | 27 | 28 | class Connectable(object): 29 | __slots__ = ("connections") 30 | 31 | signals = [] 32 | 33 | def __init__(self): 34 | self.connections = None 35 | 36 | def emit(self, signal, value=None): 37 | """ 38 | Calls all slot methods connected with signal, 39 | optionally passing in a value 40 | 41 | signal - the name of the signal to emit, must be defined in the classes 'signals' list. 42 | value - the value to pass to all connected slot methods. 43 | """ 44 | results = [] 45 | if self.connections and signal in self.connections: 46 | for obj, conditions in iteritems(self.connections[signal]): 47 | for condition, values in iteritems(conditions): 48 | if condition is None or condition == value: 49 | for overrideValue, slots in iteritems(values): 50 | if overrideValue is not None: 51 | usedValue = overrideValue 52 | if type(overrideValue) in (str, unicode): 53 | usedValue = usedValue.replace('${value}', str(value)) 54 | else: 55 | usedValue = value 56 | 57 | for slot in slots: 58 | if not hasattr(obj, slot): 59 | print(obj.__class__.__name__ + 60 | " slot not defined: " + slot) 61 | return False 62 | 63 | slotMethod = getattr(obj, slot) 64 | if usedValue is not None: 65 | if(MethodUtils.acceptsArguments(slotMethod, 1)): 66 | results.append(slotMethod(usedValue)) 67 | elif(MethodUtils.acceptsArguments(slotMethod, 0)): 68 | results.append(slotMethod()) 69 | else: 70 | results.append('') 71 | 72 | else: 73 | results.append(slotMethod()) 74 | 75 | return results 76 | 77 | def connect(self, signal, condition, receiver, slot, value=None): 78 | """ 79 | Defines a connection between this objects signal 80 | and another objects slot: 81 | 82 | signal - the signal this class will emit, to cause the slot method to be called. 83 | condition - only call the slot method if the value emitted matches this condition. 84 | receiver - the object containing the slot method to be called. 85 | slot - the name of the slot method to call. 86 | value - an optional value override to pass into the slot method as the first variable. 87 | """ 88 | if not signal in self.signals: 89 | print("%(name)s is trying to connect a slot to an undefined signal: %(signal)s" % 90 | {'name':self.__class__.__name__, 'signal':unicode(signal)}) 91 | return 92 | 93 | if self.connections is None: 94 | self.connections = {} 95 | connections = self.connections.setdefault(signal, {}) 96 | connection = connections.setdefault(receiver, {}) 97 | connection = connection.setdefault(condition, {}) 98 | connection = connection.setdefault(value, []) 99 | if not slot in connection: 100 | connection.append(slot) 101 | 102 | def disconnect(self, signal=None, condition=None, 103 | obj=None, slot=None, value=None): 104 | """ 105 | Removes connection(s) between this objects signal 106 | and connected slot(s): 107 | 108 | signal - the signal this class will emit, to cause the slot method to be called. 109 | condition - only call the slot method if the value emitted matches this condition. 110 | receiver - the object containing the slot method to be called. 111 | slot - the name of the slot method to call. 112 | value - an optional value override to pass into the slot method as the first variable. 113 | """ 114 | if slot: 115 | connection = self.connections[signal][obj][condition][value] 116 | connection.remove(slot) 117 | elif obj: 118 | self.connections[signal].pop(obj) 119 | elif signal: 120 | self.connections.pop(signal, None) 121 | else: 122 | self.connections = None 123 | -------------------------------------------------------------------------------- /thedom/controllers/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | DynamicForm/__init__.py 3 | 4 | DynamicForm is a library that allows you to use basic python classes to define how complex ajax requests 5 | and structures should be handled. 6 | 7 | Copyright (C) 2015 Timothy Edmund Crosley 8 | 9 | This program is free software; you can redistribute it and/or 10 | modify it under the terms of the GNU General Public License 11 | as published by the Free Software Foundation; either version 2 12 | of the License, or (at your option) any later version. 13 | 14 | This program is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with this program; if not, write to the Free Software 21 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 22 | """ 23 | 24 | __version__ = "0.8.0" 25 | -------------------------------------------------------------------------------- /thedom/controllers/app_engine.py: -------------------------------------------------------------------------------- 1 | """ 2 | Defines an AppEngine compatible version of DynamicForm 3 | """ 4 | 5 | import webapp2 6 | from webapp2_extras import sessions 7 | from . import HTTP, PageControls 8 | from .DynamicForm import DynamicForm 9 | 10 | 11 | def adapt(dynamicForm): 12 | """ 13 | Takes a dynamicForm class and returns an optimized app engine request handler 14 | """ 15 | class Adaptor(webapp2.RequestHandler): 16 | """ 17 | Overrides handler methods of the DynamicForm class to enable it to run seamlessly on AppEngine 18 | """ 19 | form = dynamicForm() 20 | 21 | def get(self): 22 | response = self.form.handleRequest(HTTP.Request.fromAppEngineRequest(self.request, "GET", self.session)) 23 | return response.toAppEngineResponse(self.response) 24 | 25 | def post(self): 26 | response = self.form.handleRequest(HTTP.Request.fromAppEngineRequest(self.request, "POST", self.session)) 27 | return response.toAppEngineResponse(self.response) 28 | 29 | def put(self): 30 | response = self.form.handleRequest(HTTP.Request.fromAppEngineRequest(self.request, "PUT", self.session)) 31 | return response.toAppEngineResponse(self.response) 32 | 33 | def head(self): 34 | response = self.form.handleRequest(HTTP.Request.fromAppEngineRequest(self.request, "HEAD", self.session)) 35 | return response.toAppEngineResponse(self.response) 36 | 37 | def options(self): 38 | response = self.form.handleRequest(HTTP.Request.fromAppEngineRequest(self.request, "OPTIONS", self.session)) 39 | return response.toAppEngineResponse(self.response) 40 | 41 | def delete(self): 42 | response = self.form.handleRequest(HTTP.Request.fromAppEngineRequest(self.request, "DELETE", self.session)) 43 | return response.toAppEngineResponse(self.response) 44 | 45 | def trace(self): 46 | response = self.form.handleRequest(HTTP.Request.fromAppEngineRequest(self.request, "TRACE", self.session)) 47 | return response.toAppEngineResponse(self.response) 48 | 49 | def dispatch(self): 50 | """ 51 | This snippet of code is taken from the webapp2 framework documentation. 52 | See more at 53 | http://webapp-improved.appspot.com/api/webapp2_extras/sessions.html 54 | 55 | """ 56 | self.session_store = sessions.get_store(request=self.request) 57 | try: 58 | webapp2.RequestHandler.dispatch(self) 59 | finally: 60 | self.session_store.save_sessions(self.response) 61 | 62 | @webapp2.cached_property 63 | def session(self): 64 | """ 65 | This snippet of code is taken from the webapp2 framework documentation. 66 | See more at 67 | http://webapp-improved.appspot.com/api/webapp2_extras/sessions.html 68 | 69 | """ 70 | return self.session_store.get_session() 71 | 72 | return Adaptor 73 | -------------------------------------------------------------------------------- /thedom/controllers/dynamic_form.py: -------------------------------------------------------------------------------- 1 | ''' 2 | DynamicForm.py 3 | 4 | Defines the DynamicForm concept (a page made up entirely of page controls / request handlers so that 5 | any part of the page can be updated independently 6 | 7 | Copyright (C) 2015 Timothy Edmund Crosley 8 | 9 | This program is free software; you can redistribute it and/or 10 | modify it under the terms of the GNU General Public License 11 | as published by the Free Software Foundation; either version 2 12 | of the License, or (at your option) any later version. 13 | 14 | This program is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with this program; if not, write to the Free Software 21 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 22 | ''' 23 | 24 | from itertools import chain 25 | 26 | from thedom.All import Factory 27 | from thedom import UITemplate, ListUtils 28 | from thedom.Document import Document 29 | from thedom.HiddenInputs import HiddenValue 30 | from thedom.Compile import CompiledTemplate 31 | from . import PageControls 32 | from .RequestHandler import RequestHandler 33 | from thedom.Resources import ResourceFile, ScriptContainer 34 | 35 | try: 36 | from django.core.context_processors import csrf 37 | except ImportError: 38 | csrf = False 39 | 40 | 41 | class DynamicForm(RequestHandler): 42 | """ 43 | Defines the base dynamic form - a page made where any section can be updated independently from the rest 44 | """ 45 | elementFactory = Factory 46 | formatted = False 47 | resourceFiles = ('js/WebBot.js', 'stylesheets/Site.css') 48 | if csrf: 49 | sharedFields = ('csrfmiddlewaretoken', ) 50 | 51 | def renderResponse(self, request): 52 | """ 53 | Override the response rendering to render the main document structure of the page 54 | """ 55 | document = Document() 56 | request.response.scripts = ScriptContainer() 57 | request.response.scripts.addScript("".join(self.initScripts)) 58 | document.setScriptContainer(request.response.scripts) 59 | document.setProperty('title', self.title(request)) 60 | document.addChildElement(ResourceFile()).setProperty("file", self.favicon(request)) 61 | document.addMetaData(value="IE=Edge", **{"http-equiv":'X-UA-Compatible'}) 62 | for resourceFile in ListUtils.unique(self.requestResourceFiles(request) + self.resourceFiles): 63 | document.addChildElement(ResourceFile()).setProperty("file", resourceFile) 64 | 65 | if csrf: 66 | token = document.body.addChildElement(HiddenValue('csrfmiddlewaretoken')) 67 | token.setValue(csrf(request.native)['csrf_token']) 68 | document.body += self.mainControl 69 | document.body += request.response.scripts 70 | 71 | self.modifyDocument(document, request) 72 | 73 | return document.toHTML(formatted=self.formatted, request=request) 74 | 75 | def modifyDocument(self, document, request): 76 | """ 77 | Override to change the structure of the base document 78 | """ 79 | pass 80 | 81 | def title(self, request): 82 | """ 83 | Returns the title of the page - by default this is the class name of the DynamicForm subclass 84 | override this method to change that 85 | """ 86 | return self.__class__.__name__ 87 | 88 | def favicon(self, request): 89 | """ 90 | Returns the title of the page - by default this is the class name of the DynamicForm subclass 91 | override this method to change that 92 | """ 93 | return "images/favicon.png" 94 | 95 | def requestResourceFiles(self, request): 96 | """ 97 | Returns the resource files that should be loaded with this page by request - override this to change 98 | """ 99 | return () 100 | 101 | class MainControl(PageControls.PageControl): 102 | """ 103 | Override this controller to define how the body of the page should render 104 | """ 105 | pass 106 | -------------------------------------------------------------------------------- /thedom/dict_utils.py: -------------------------------------------------------------------------------- 1 | ''' 2 | DictUtils.py 3 | 4 | Utility methods and classes that provide more efficient ways of handling python dictionaries 5 | 6 | Copyright (C) 2015 Timothy Edmund Crosley 7 | 8 | This program is free software; you can redistribute it and/or 9 | modify it under the terms of the GNU General Public License 10 | as published by the Free Software Foundation; either version 2 11 | of the License, or (at your option) any later version. 12 | 13 | This program is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with this program; if not, write to the Free Software 20 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 21 | ''' 22 | 23 | from .MultiplePythonSupport import * 24 | 25 | 26 | def missingKey(d1, d2): 27 | """ 28 | Returns a list of name value pairs for all the elements that are present in one dictionary and not the other 29 | """ 30 | l = [] 31 | l += [ {k:d1[k]} for k in d1 if k not in d2 ] 32 | l += [ {k:d2[k]} for k in d2 if k not in d1 ] 33 | return l 34 | 35 | def dictCompare(d1, d2): 36 | """ 37 | Returns a list of name value pairs for all the elements that are different between the two dictionaries 38 | """ 39 | diffs = missingKey(d1, d2) 40 | diffs += [ {k:str(d1[k]) + '->' + str(d2[k])} for k in d1 if k in d2 and d1[k] != d2[k]] 41 | return diffs 42 | 43 | def userInputStrip(uDict): 44 | """ 45 | Strip whitespace out of input provided by the user 46 | """ 47 | dictList = map(lambda x: (x[1] and type(x[1]) == type('')) and (x[0], x[1].strip()) or (x[0], x[1]), uDict.items()) 48 | return dict(dictList) 49 | 50 | def setNestedValue(d, keyString, value): 51 | """ 52 | Sets the value in a nested dictionary where '.' is the delimiter 53 | """ 54 | keys = keyString.split('.') 55 | currentValue = d 56 | for key in keys: 57 | previousValue = currentValue 58 | currentValue = currentValue.setdefault(key, {}) 59 | previousValue[key] = value 60 | 61 | def getNestedValue(dictionary, keyString, default=None): 62 | """ 63 | Returns the value from a nested dictionary where '.' is the delimiter 64 | """ 65 | keys = keyString.split('.') 66 | currentValue = dictionary 67 | for key in keys: 68 | if not isinstance(currentValue, dict): 69 | return default 70 | currentValue = currentValue.get(key, None) 71 | if currentValue is None: 72 | return default 73 | 74 | return currentValue 75 | 76 | def stringKeys(dictionary): 77 | """ 78 | Modifies the passed in dictionary to ensure all keys are string objects, converting them when necessary. 79 | """ 80 | for key, value in dictionary.items(): 81 | if type(key) != str: 82 | dictionary.pop(key) 83 | dictionary[str(key)] = value 84 | 85 | return dictionary 86 | 87 | def iterateOver(dictionary, key): 88 | """ 89 | Returns a list version of the value associated with key in dictionary. 90 | """ 91 | results = dictionary.get(key, []) 92 | if type(results) != list: 93 | results = [results] 94 | 95 | return enumerate(results) 96 | 97 | def twoWayDict(dictionary): 98 | """ 99 | Doubles the size of the dictionary, by making every reverse lookup work. 100 | """ 101 | for key, value in dictionary.items(): 102 | dictionary[value] = key 103 | 104 | return dictionary 105 | 106 | class OrderedDict(dict): 107 | """ 108 | Defines a dictionary which maintains order - only necessary in older versions of python. 109 | """ 110 | class ItemIterator(dict): 111 | 112 | def __init__(self, orderedDict, includeIndex=False): 113 | self.orderedDict = orderedDict 114 | self.length = len(orderedDict) 115 | self.index = 0 116 | self.includeIndex = includeIndex 117 | 118 | def next(self): 119 | if self.index < self.length: 120 | key = self.orderedDict.orderedKeys[self.index] 121 | value = self.orderedDict[key] 122 | to_return = (self.includeIndex and (self.index, key, value)) or (key, value) 123 | self.index += 1 124 | return to_return 125 | else: 126 | raise StopIteration 127 | 128 | def __iter__(self): 129 | return self 130 | 131 | def __init__(self, tuplePairs=()): 132 | self.orderedKeys = [] 133 | 134 | for key, value in tuplePairs: 135 | self[key] = value 136 | 137 | def __add__(self, value): 138 | if isinstance(value, OrderedDict): 139 | newDict = self.copy() 140 | newDict.update(value) 141 | return newDict 142 | 143 | return dict.__add__(self, value) 144 | 145 | def copy(self): 146 | newDict = OrderedDict() 147 | newDict.update(self) 148 | return newDict 149 | 150 | def update(self, dictionary): 151 | for key, value in iteritems(dictionary): 152 | self[key] = value 153 | 154 | def items(self): 155 | items = [] 156 | for key in self.orderedKeys: 157 | items.append((key, self[key])) 158 | 159 | return items 160 | 161 | def values(self): 162 | values = [] 163 | for key in self.orderedKeys: 164 | values.append(self[key]) 165 | 166 | return values 167 | 168 | def keys(self): 169 | return self.orderedKeys 170 | 171 | def iterkeys(self): 172 | return self.orderedKeys.__iter__() 173 | 174 | def __iter__(self): 175 | return self.iterkeys() 176 | 177 | def iteritems(self): 178 | return self.ItemIterator(self) 179 | 180 | def iteritemsWithIndex(self): 181 | return self.ItemIterator(self, includeIndex=True) 182 | 183 | def __setitem__(self, keyString, value): 184 | if not keyString in self.orderedKeys: 185 | self.orderedKeys.append(keyString) 186 | return dict.__setitem__(self, keyString, value) 187 | 188 | def setdefault(self, keyString, value): 189 | if not keyString in self.orderedKeys: 190 | self.orderedKeys.append(keyString) 191 | return dict.setdefault(self, keyString, value) 192 | 193 | def getAllNestedKeys(dictionary, prefix=""): 194 | """ 195 | Returns all keys nested within nested dictionaries. 196 | """ 197 | keys = [] 198 | for key, value in iteritems(dictionary): 199 | if isinstance(value, dict): 200 | keys.extend(getAllNestedKeys(value, prefix=prefix + key + '.')) 201 | continue 202 | 203 | keys.append(prefix + key) 204 | 205 | return keys 206 | 207 | 208 | class NestedDict(dict): 209 | """ 210 | Defines a dictionary that enables easy safe retrieval of nested dict keys. 211 | """ 212 | def __init__(self, d=None): 213 | if d: 214 | self.update(d) 215 | 216 | def setValue(self, keyString, value): 217 | """ 218 | Sets the value of a nested dict key. 219 | """ 220 | setNestedValue(self, keyString, value) 221 | 222 | def allKeys(self): 223 | """ 224 | Returns all keys, including nested dict keys. 225 | """ 226 | return getAllNestedKeys(self) 227 | 228 | def difference(self, otherDict): 229 | """ 230 | returns a list of tuples [(key, myValue, otherDictValue),] 231 | allowing you to do: 232 | for fieldName, oldValue, newValue in oldValues.difference(newValues) 233 | """ 234 | differences = [] 235 | for key in set(self.allKeys() + otherDict.allKeys()): 236 | myValue = self.getValue(key, default=None) 237 | otherDictValue = otherDict.getValue(key, default=None) 238 | if myValue != otherDictValue: 239 | differences.append((key, myValue, otherDictValue)) 240 | 241 | return differences 242 | 243 | 244 | def getValue(self, keyString, **kwargs): 245 | """ 246 | Returns a nested value if it exists. 247 | """ 248 | keys = keyString.split('.') 249 | currentNode = self 250 | for key in keys: 251 | if not key: 252 | continue 253 | currentNode = currentNode.get(key, None) 254 | if not currentNode: 255 | break 256 | 257 | if currentNode: 258 | return currentNode 259 | elif 'default' in kwargs: 260 | return kwargs.get('default') 261 | else: 262 | raise KeyError(keyString) 263 | 264 | 265 | def createDictFromString(string, itemSeparator, keyValueSeparator, ordered=False): 266 | """ 267 | Creates a new dictionary based on the passed in string, itemSeparator and keyValueSeparator. 268 | """ 269 | if ordered: 270 | newDict = OrderedDict() 271 | else: 272 | newDict = {} 273 | if not string: 274 | return newDict 275 | for item in string.split(itemSeparator): 276 | key, value = item.split(keyValueSeparator) 277 | oldValue = newDict.get(key, None) 278 | if oldValue is not None: 279 | if type(oldValue) == list: 280 | newDict[key].append(value) 281 | else: 282 | newDict[key] = [oldValue, value] 283 | else: 284 | newDict[key] = value 285 | 286 | return newDict 287 | -------------------------------------------------------------------------------- /thedom/document.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Document.py 3 | 4 | Provides elements that define the html document being served to the client-side 5 | 6 | Copyright (C) 2015 Timothy Edmund Crosley 7 | 8 | This program is free software; you can redistribute it and/or 9 | modify it under the terms of the GNU General Public License 10 | as published by the Free Software Foundation; either version 2 11 | of the License, or (at your option) any later version. 12 | 13 | This program is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with this program; if not, write to the Free Software 20 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 21 | ''' 22 | 23 | from . import Base, Factory 24 | from .MethodUtils import CallBack 25 | from .MultiplePythonSupport import * 26 | from .Resources import ResourceFile 27 | 28 | Factory = Factory.Factory("Document") 29 | 30 | DOCTYPE_XHTML_TRANSITIONAL = ('') 32 | DOCTYPE_XHTML_STRICT = ('') 34 | DOCTYPE_XHTML_FRAMESET = ('') 36 | DOCTYPE_HTML4_TRANSITIONAL = ('') 38 | DOCTYPE_HTML4_STRICT = ('') 40 | DOCTYPE_HTML4_FRAMESET = ('') 42 | DOCTYPE_HTML5 = "" 43 | 44 | class MetaData(Base.Node): 45 | """ 46 | A webelement implementation of the meta tag 47 | """ 48 | __slots__ = () 49 | tagName = "meta" 50 | displayable = False 51 | 52 | properties = Base.Node.properties.copy() 53 | properties['value'] = {'action':'setValue'} 54 | properties['name'] = {'action':'setName'} 55 | properties['http-equiv'] = {'action':'attribute'} 56 | 57 | def _create(self, id=None, name=None, parent=None, **kwargs): 58 | Base.Node._create(self) 59 | 60 | def value(self): 61 | """ 62 | Returns the meta tags value 63 | """ 64 | return self.attributes.get('content') 65 | 66 | def setValue(self, value): 67 | """ 68 | Sets the meta tags value 69 | """ 70 | self.attributes['content'] = value 71 | 72 | def getName(self): 73 | """ 74 | Returns the name of the meta tag 75 | """ 76 | return self.name 77 | 78 | def setName(self, name): 79 | """ 80 | Sets the name of the meta tag 81 | """ 82 | self.name = name 83 | 84 | def shown(self): 85 | """ 86 | Meta tags are never visible 87 | """ 88 | return False 89 | 90 | Factory.addProduct(MetaData) 91 | 92 | 93 | class HTTPHeader(MetaData): 94 | """ 95 | A webelement that represents an http header meta tag 96 | """ 97 | __slots__ = () 98 | def getName(self): 99 | """ 100 | Returns the headers name 101 | """ 102 | return self.attributes.get('http-equiv') 103 | 104 | def setName(self, name): 105 | """ 106 | Sets the headers name 107 | """ 108 | self.attributes['http-equiv'] = name 109 | 110 | Factory.addProduct(HTTPHeader) 111 | 112 | 113 | class Document(Base.Node): 114 | """ 115 | A Node representation of the overall document that fills a single page 116 | """ 117 | __slots__ = ('head', 'body', 'title', 'contentType') 118 | doctype = DOCTYPE_HTML5 119 | tagName = "html" 120 | properties = Base.Node.properties.copy() 121 | properties['doctype'] = {'action':'classAttribute'} 122 | properties['title'] = {'action':'title.setText'} 123 | properties['contentType'] = {'action':'contentType.setValue'} 124 | properties['xmlns'] = {'action':'attribute'} 125 | 126 | class Head(Base.Node): 127 | """ 128 | Documents Head 129 | """ 130 | tagName = "head" 131 | 132 | class Body(Base.Node): 133 | """ 134 | Documents Body 135 | """ 136 | tagName = "body" 137 | 138 | class Title(Base.Node): 139 | """ 140 | Documents Title 141 | """ 142 | tagName = "title" 143 | 144 | def _create(self, id=None, name=None, parent=None, **kwargs): 145 | Base.Node._create(self, id=id, name=name, parent=parent) 146 | 147 | self._textNode = self.add(Base.TextNode()) 148 | 149 | 150 | def setText(self, text): 151 | """ 152 | Sets the document title 153 | """ 154 | self._textNode.setText(text) 155 | 156 | def text(self): 157 | """ 158 | Returns the document title 159 | """ 160 | return self._textNode.text(text) 161 | 162 | def _create(self, id=None, name=None, parent=None, **kwargs): 163 | Base.Node._create(self) 164 | self.head = self.add(self.Head()) 165 | self.body = self.add(self.Body()) 166 | self.title = self.head.add(self.Title()) 167 | self.contentType = self.addHeader('Content-Type', 'text/html; charset=UTF-8') 168 | 169 | def addMetaData(self, name=None, value="", **kwargs): 170 | """ 171 | Will add a meta tag based on name+value pair 172 | """ 173 | metaTag = self.head.add(MetaData(**kwargs)) 174 | metaTag.setName(name) 175 | metaTag.setValue(value) 176 | return metaTag 177 | 178 | def addHeader(self, name, value): 179 | """ 180 | Will add an HTTP header pair based on name + value pair 181 | """ 182 | header = self.head.add(HTTPHeader()) 183 | header.setName(name) 184 | header.setValue(value) 185 | return header 186 | 187 | def toHTML(self, formatted=False, *args, **kwargs): 188 | """ 189 | Overrides toHTML to include the doctype definition before the open tag. 190 | """ 191 | return self.doctype + "\n" + Base.Node.toHTML(self, formatted, *args, **kwargs) 192 | 193 | def add(self, childElement, ensureUnique=True): 194 | """ 195 | Overrides add to place header elements and resources in the head 196 | and all others in the body. 197 | """ 198 | if type(childElement) in [self.Head, self.Body]: 199 | return Base.Node.add(self, childElement, ensureUnique) 200 | elif type(childElement) == ResourceFile or childElement._tagName in ['title', 'base', 'link', 201 | 'meta', 'script', 'style']: 202 | return self.head.add(childElement, ensureUnique) 203 | else: 204 | return self.body.add(childElement, ensureUnique) 205 | 206 | Head = Document.Head 207 | Body = Document.Body 208 | Title = Document.Title 209 | Factory.addProduct(Document) 210 | -------------------------------------------------------------------------------- /thedom/factory.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Factory.py 3 | 4 | The Node Factory provides a mechanism for building any element the factory has knowledge of 5 | simply by defining its name (as a string) and its main attributes, as well as providing the ability 6 | to combine multiple factories into one 7 | 8 | Copyright (C) 2015 Timothy Edmund Crosley 9 | 10 | This program is free software; you can redistribute it and/or 11 | modify it under the terms of the GNU General Public License 12 | as published by the Free Software Foundation; either version 2 13 | of the License, or (at your option) any later version. 14 | 15 | This program is distributed in the hope that it will be useful, 16 | but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | GNU General Public License for more details. 19 | 20 | You should have received a copy of the GNU General Public License 21 | along with this program; if not, write to the Free Software 22 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 23 | ''' 24 | 25 | import types 26 | 27 | from .Base import Invalid, TextNode 28 | from .MultiplePythonSupport import * 29 | 30 | 31 | class Factory(object): 32 | def __init__(self, name=""): 33 | self.products = {} 34 | self.name = name 35 | 36 | def addProduct(self, productClass): 37 | """ 38 | Adds a Node to the list of products that can be built from the factory: 39 | productClass - the Node's class 40 | """ 41 | self.products[productClass.__name__.lower()] = productClass 42 | 43 | def build(self, className, id=None, name=None, parent=None): 44 | """ 45 | Builds a Node instance from the className: 46 | className - the class name of the webElement (case insensitive) 47 | id - the unique id to assign to the newly built element 48 | name - the non-unique identifier to asign to the newly built element 49 | parent - the element that will contain the newly built element 50 | """ 51 | className = className and className.lower() or "" 52 | product = self.products.get(className, None) 53 | if product: 54 | return product(id, name, parent) 55 | else: 56 | print(self.name + " has no product " + className + " sorry :(") 57 | return Invalid() 58 | 59 | def buildFromTemplate(self, template, variableDict=None, idPrefix=None, parent=None, 60 | scriptContainer=None, accessors=None): 61 | """ 62 | Builds an Node or a tree of web elements from a dictionary definition: 63 | template - the Node template node definition tree 64 | variableDict - a dictionary of variables (id/name/key):value to use to populate the 65 | tree of thedom 66 | idPrefix - a prefix to prepend before each element id in the tree to distinguish it 67 | from a different tree on the page 68 | parent - the webElement that will encompass the tree 69 | scriptContainer - a container (AJAXScriptContainer/ScriptContainer) to throw scripts 70 | in 71 | accessors - pass in a dictionary to have it updated with element accessors 72 | """ 73 | if not template: 74 | return Invalid() 75 | 76 | if type(template) in (str, unicode): 77 | return TextNode(template) 78 | 79 | ID = template.id 80 | accessor = template.accessor 81 | 82 | elementObject = self.build(template.create, ID, template.name, parent) 83 | if idPrefix and not elementObject._prefix: 84 | elementObject.setPrefix(idPrefix) 85 | elementObject.setScriptContainer(scriptContainer) 86 | elementObject.setProperties(template.properties) 87 | if accessors is not None: 88 | if accessor: 89 | accessors[accessor] = elementObject 90 | elif ID: 91 | accessors[ID] = elementObject 92 | 93 | if elementObject.allowsChildren: 94 | add = elementObject.add 95 | buildFromTemplate = self.buildFromTemplate 96 | addsTo = elementObject.addsTo 97 | for child in template.childElements or (): 98 | childElement = buildFromTemplate(child, parent=addsTo, accessors=accessors) 99 | add(childElement) 100 | if variableDict: 101 | elementObject.insertVariables(variableDict) 102 | 103 | return elementObject 104 | 105 | class Composite(Factory): 106 | """ 107 | Allows you to combine one or more web elements factories to build a composite factory. 108 | 109 | If two or more elements identically named elements are contained within the factories -- 110 | the last factory passed in will override the definition of the element. 111 | """ 112 | def __init__(self, factories): 113 | Factory.__init__(self) 114 | 115 | for factory in factories: 116 | self.products.update(factory.products) 117 | if factory.name: 118 | for productName, product in iteritems(factory.products): 119 | self.products[factory.name.lower() + "-" + productName] = product 120 | -------------------------------------------------------------------------------- /thedom/hidden_inputs.py: -------------------------------------------------------------------------------- 1 | ''' 2 | HiddenInputs.py 3 | 4 | Contains a collection of inputs that are not displayed to the user, but are passed to the server 5 | 6 | Copyright (C) 2015 Timothy Edmund Crosley 7 | 8 | This program is free software; you can redistribute it and/or 9 | modify it under the terms of the GNU General Public License 10 | as published by the Free Software Foundation; either version 2 11 | of the License, or (at your option) any later version. 12 | 13 | This program is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with this program; if not, write to the Free Software 20 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 21 | ''' 22 | 23 | from . import Base, Factory 24 | from .Inputs import InputElement 25 | from .MethodUtils import CallBack 26 | from .MultiplePythonSupport import * 27 | 28 | Factory = Factory.Factory("HiddenInputs") 29 | 30 | 31 | class HiddenValue(InputElement): 32 | """ 33 | Defines a hidden '' webelement (An input that can be modified but not viewed clientside) 34 | """ 35 | __slots__ = ('width', ) 36 | signals = InputElement.signals + ['textChanged'] 37 | displayable = False 38 | 39 | def _create(self, id=None, name=None, parent=None, key=None): 40 | InputElement._create(self, id, name, parent, key=key) 41 | self.attributes['type'] = "hidden" 42 | 43 | self.width = "hidden" 44 | 45 | def setText(self, text): 46 | """ 47 | Sets the hidden inputs value 48 | """ 49 | if text != self.value(): 50 | self.setValue(text) 51 | self.emit('textChanged', text) 52 | 53 | def shown(self): 54 | """ 55 | A hiddenInput is never visible 56 | """ 57 | return False 58 | 59 | def text(self): 60 | """ 61 | Returns the hidden inputs value 62 | """ 63 | return self.value() 64 | 65 | Factory.addProduct(HiddenValue) 66 | 67 | 68 | class HiddenBooleanValue(HiddenValue): 69 | """ 70 | Defines a hidden value which accepts true or false values 71 | """ 72 | __slots__ = () 73 | 74 | def _create(self, id=None, name=None, parent=None, key=None): 75 | HiddenValue._create(self, id, name, parent, key=key) 76 | self.attributes['value'] = '' 77 | 78 | def setValue(self, value): 79 | """ 80 | Sets the hidden inputs value to 1 or '' to represent true or false 81 | """ 82 | if value: 83 | HiddenValue.setValue(self, True) 84 | self.attributes['value'] = '1' 85 | else: 86 | HiddenValue.setValue(self, False) 87 | self.attributes['value'] = '' 88 | 89 | def value(self): 90 | """ 91 | Returns the true or false value of the input 92 | """ 93 | return bool(HiddenValue.value(self)) 94 | 95 | Factory.addProduct(HiddenBooleanValue) 96 | 97 | 98 | class HiddenIntValue(HiddenValue): 99 | """ 100 | Defines a hidden value which accepts integer values only 101 | """ 102 | __slots__ = () 103 | 104 | def _create(self, id=None, name=None, parent=None, key=None): 105 | HiddenValue._create(self, id, name, parent, key=key) 106 | self.attributes['value'] = 0 107 | 108 | def setValue(self, value): 109 | """ 110 | Sets the value of the input as an integer 111 | """ 112 | if (value is None) or (value == ''): 113 | value = 0 114 | 115 | HiddenValue.setValue(self, int(value)) 116 | self.attributes['value'] = unicode(value) 117 | 118 | def value(self): 119 | """ 120 | Returns the integer value of the input 121 | """ 122 | return int(HiddenValue.value(self) or 0) 123 | 124 | Factory.addProduct(HiddenIntValue) 125 | -------------------------------------------------------------------------------- /thedom/json_parser.py: -------------------------------------------------------------------------------- 1 | ''' 2 | JsonParser.py 3 | 4 | Creates a Node tree from a python data structure (presumably originating from a JSON string) 5 | 6 | Copyright (C) 2015 Timothy Edmund Crosley 7 | 8 | This program is free software; you can redistribute it and/or 9 | modify it under the terms of the GNU General Public License 10 | as published by the Free Software Foundation; either version 2 11 | of the License, or (at your option) any later version. 12 | 13 | This program is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with this program; if not, write to the Free Software 20 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 21 | ''' 22 | 23 | from .Base import Node, TextNode 24 | from .Layout import Flow 25 | from .MultiplePythonSupport import * 26 | 27 | TYPE_MAP = {str:'string', unicode:'string', int:'integer'} 28 | 29 | class __Tag__(Node): 30 | def _create(self, tagName, parent, id=None, name=None): 31 | Node._create(self, id, name, parent, **kwargs) 32 | self._tagName = tagName 33 | 34 | def parse(data, formatted=False): 35 | """ 36 | Takes a jsonable python data structure and turns it into valid xml 37 | """ 38 | tree = __parse__(data, Flow()) 39 | tree[0].attributes['xmlns:xsi'] = "http://www.w3.org/2001/XMLSchema-instance" 40 | tree[0].attributes['xmlns:xsd'] = "http://www.w3.org/2001/XMLSchema" 41 | return tree.toHTML(formatted=formatted) 42 | 43 | def __parse__(data, parentElement): 44 | for key, value in iteritems(data): 45 | newElement = parentElement.add(__Tag__(key, parentElement)) 46 | if type(value) == dict: 47 | __parse__(value, newElement) 48 | elif type(value) in (list, tuple): 49 | for item in value: 50 | newElement.add(__Tag__(TYPE_MAP[type(item)], newElement)).add(TextNode(item)) 51 | elif value is None: 52 | newElement.tagSelfCloses = newElement.attributes['xsi:nil'] = True 53 | else: 54 | newElement.add(TextNode(value)) 55 | return parentElement 56 | -------------------------------------------------------------------------------- /thedom/list_utils.py: -------------------------------------------------------------------------------- 1 | ''' 2 | ListUtils.py 3 | 4 | Utility methods and classes that provide more efficient ways of handling python lists 5 | 6 | Copyright (C) 2015 Timothy Edmund Crosley 7 | 8 | This program is free software; you can redistribute it and/or 9 | modify it under the terms of the GNU General Public License 10 | as published by the Free Software Foundation; either version 2 11 | of the License, or (at your option) any later version. 12 | 13 | This program is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with this program; if not, write to the Free Software 20 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 21 | ''' 22 | 23 | try: 24 | from collections import OrderedDict 25 | HAS_ORDERED_DICT = True 26 | except ImportError: 27 | HAS_ORDERED_DICT = False 28 | 29 | 30 | if HAS_ORDERED_DICT: 31 | def unique(nonUnique): 32 | """ Takes a non unique list and returns a unique one, preserving order """ 33 | return list(OrderedDict.fromkeys(nonUnique)) 34 | else: 35 | def unique(nonUnique): 36 | """ Takes a non unique list and returns a unique one, preserving order """ 37 | seen = {} 38 | results = [] 39 | for item in nonUnique: 40 | if item in seen: 41 | continue 42 | seen[item] = 1 43 | results.append(item) 44 | return results 45 | -------------------------------------------------------------------------------- /thedom/method_utils.py: -------------------------------------------------------------------------------- 1 | ''' 2 | MethodUtils.py 3 | 4 | A collection of functions to aid in method introspection and usage 5 | 6 | Copyright (C) 2015 Timothy Edmund Crosley 7 | 8 | This program is free software; you can redistribute it and/or 9 | modify it under the terms of the GNU General Public License 10 | as published by the Free Software Foundation; either version 2 11 | of the License, or (at your option) any later version. 12 | 13 | This program is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with this program; if not, write to the Free Software 20 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 21 | ''' 22 | 23 | from .MultiplePythonSupport import * 24 | 25 | 26 | def acceptsArguments(method, numberOfArguments): 27 | """ 28 | Returns True if the given method will accept the given number of arguments: 29 | method - the method to perform introspection on 30 | numberOfArguments - the numberOfArguments 31 | """ 32 | if 'method' in method.__class__.__name__: 33 | numberOfArguments += 1 34 | func = getattr(method, 'im_func', getattr(method, '__func__')) 35 | funcDefaults = getattr(func, 'func_defaults', getattr(func, '__defaults__')) 36 | numberOfDefaults = funcDefaults and len(funcDefaults) or 0 37 | elif method.__class__.__name__ == 'function': 38 | funcDefaults = getattr(method, 'func_defaults', getattr(method, '__defaults__')) 39 | numberOfDefaults = funcDefaults and len(funcDefaults) or 0 40 | coArgCount = getattr(method, 'func_code', getattr(method, '__code__')).co_argcount 41 | if(coArgCount >= numberOfArguments and coArgCount - numberOfDefaults <= numberOfArguments): 42 | return True 43 | 44 | return False 45 | 46 | 47 | class CallBack(object): 48 | """ 49 | Enables objects to be passed around in a copyable/pickleable way 50 | """ 51 | 52 | def __init__(self, obj, method, argumentDict=None): 53 | """ 54 | Creates the call back object: 55 | obj - the actual object that the method will be called on 56 | method - the name of the method to call 57 | """ 58 | self.toCall = method 59 | self.obj = obj 60 | self.argumentDict = argumentDict 61 | 62 | def __call__(self): 63 | return self.call() 64 | 65 | def __str__(self): 66 | return unicode(self.call() or "") 67 | 68 | def call(self): 69 | """ 70 | Calls the method 71 | """ 72 | if self.argumentDict: 73 | return self.obj.__getattribute__(self.toCall)(**self.argumentDict) 74 | else: 75 | return self.obj.__getattribute__(self.toCall)() 76 | -------------------------------------------------------------------------------- /thedom/position_controller.py: -------------------------------------------------------------------------------- 1 | ''' 2 | PositionController.py 3 | 4 | Provides a class that isolates the logic of paging through long sets of data such as a db query 5 | 6 | Copyright (C) 2015 Timothy Edmund Crosley 7 | 8 | This program is free software; you can redistribute it and/or 9 | modify it under the terms of the GNU General Public License 10 | as published by the Free Software Foundation; either version 2 11 | of the License, or (at your option) any later version. 12 | 13 | This program is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with this program; if not, write to the Free Software 20 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 21 | ''' 22 | 23 | from .IteratorUtils import iterableLength 24 | from .MultiplePythonSupport import * 25 | 26 | 27 | class PositionController(object): 28 | """A simple way to control paging and positon within lists 29 | 30 | usage: 31 | positionController = PositionController(databaseQueryResults, startIndex, itemsPerPage) 32 | 33 | results = positionController.currentPageItems 34 | back = positionController.prevPageIndex 35 | next = positionController.nextPageIndex 36 | 37 | moreResults = positionController.areMore 38 | lessResults = positionController.arePrev 39 | """ 40 | 41 | def __init__(self, items=[], startIndex=0, itemsPerPage=25, pagesShownAtOnce=15): 42 | """ 43 | Constructs a new Position Controller Object: 44 | 45 | allItems = a python list, you are trying to retrieve sections from 46 | startIndex = where to start getting list elements from 47 | itemsPerPage = How many list elements to get on each page 48 | 49 | usage: 50 | positionController = PositionController(databaseQueryResults, startIndex, ) 51 | """ 52 | self.pagesShownAtOnce = pagesShownAtOnce 53 | self.allItems = items 54 | self.length = iterableLength(self.allItems) 55 | self.empty = not self.length 56 | self.itemsPerPage = itemsPerPage 57 | 58 | self.numberOfPages = self.length // self.itemsPerPage 59 | if self.numberOfPages < (float(self.length) / float(self.itemsPerPage)): 60 | self.numberOfPages += 1 61 | 62 | self.allPages = [] 63 | for count in range(self.numberOfPages): 64 | self.allPages.append(self.pageIndex(count)) 65 | 66 | self.lastPageIndex = 0 67 | if self.length > self.itemsPerPage: 68 | self.lastPageIndex = self.allPages[-1] 69 | 70 | self.setIndex(startIndex) 71 | 72 | def setIndex(self, index): 73 | """ 74 | Sets the index to start returning results from: 75 | index - the offset to start at 76 | """ 77 | self.startIndex = index 78 | if self.startIndex > self.length: 79 | self.startIndex = 0 80 | 81 | self.startPosition = self.startIndex + 1 82 | self.arePrev = bool(self.startPosition > 1) 83 | 84 | self.page = self.startIndex // self.itemsPerPage 85 | if self.empty: 86 | self.pageNumber = 0 87 | else: 88 | self.pageNumber = self.page + 1 89 | 90 | self.nextPageIndex = self.startIndex + self.itemsPerPage 91 | if self.nextPageIndex >= self.length: 92 | self.nextPageIndex = self.length 93 | self.areMore = False 94 | else: 95 | self.areMore = True 96 | 97 | self.currentPageItems = self.allItems[self.startIndex : self.startIndex + self.itemsPerPage] 98 | 99 | self.prevPageIndex = self.startPosition - (self.itemsPerPage + 1) 100 | if self.prevPageIndex < 0: 101 | self.prevPageIndex = 0 102 | 103 | def nextPage(self): 104 | """ 105 | Selects the next available page 106 | """ 107 | self.setIndex(self.nextPageIndex) 108 | 109 | def prevPage(self): 110 | """ 111 | Selects the previouse page 112 | """ 113 | self.setIndex(self.prevPageIndex) 114 | 115 | def setPage(self, page): 116 | """ 117 | Sets the index to the start index of pageNumber 118 | """ 119 | if page >= self.numberOfPages: 120 | page = self.numberOfPages - 1 121 | if page < 0: 122 | page = 0 123 | self.setIndex(self.pageIndex(page)) 124 | 125 | def pageIndex(self, page=0): 126 | """ 127 | Returns the index where a particular page starts: 128 | page = the page to retreive the index for 129 | """ 130 | pageIndex = self.itemsPerPage * page 131 | if pageIndex > self.length: 132 | pageIndex = self.last() 133 | 134 | return pageIndex 135 | 136 | def pageList(self): 137 | """ 138 | Returns a list of the pages around the current page, limited to pagesShownAtOnce 139 | """ 140 | pageStart = 0 141 | 142 | if self.page > self.pagesShownAtOnce // 2: 143 | pageStart = self.page - self.pagesShownAtOnce // 2 144 | 145 | pageEnd = pageStart + self.pagesShownAtOnce 146 | if pageEnd > self.numberOfPages - 1: 147 | if pageEnd - pageStart >= self.numberOfPages: 148 | pageStart = 0 149 | else: 150 | pageStart -= pageEnd - self.numberOfPages 151 | pageEnd = self.numberOfPages 152 | 153 | return self.allPages[pageStart:pageEnd] 154 | -------------------------------------------------------------------------------- /thedom/printing.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Printing.py 3 | 4 | Contains Elements that make it easy to modify the printed output of a page 5 | 6 | Copyright (C) 2015 Timothy Edmund Crosley 7 | 8 | This program is free software; you can redistribute it and/or 9 | modify it under the terms of the GNU General Public License 10 | as published by the Free Software Foundation; either version 2 11 | of the License, or (at your option) any later version. 12 | 13 | This program is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with this program; if not, write to the Free Software 20 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 21 | ''' 22 | 23 | from . import Base, Factory, Layout 24 | from .MethodUtils import CallBack 25 | from .MultiplePythonSupport import * 26 | 27 | Factory = Factory.Factory("Printing") 28 | 29 | class PageBreak(Layout.Box): 30 | """ 31 | Defines an area where a break in the page would be logical 32 | """ 33 | __slots__ = () 34 | 35 | def _create(self, id=None, name=None, parent=None, **kwargs): 36 | Layout.Box._create(self, id=id, name=name, parent=parent) 37 | 38 | self.style['page-break-before'] = "always" 39 | 40 | Factory.addProduct(PageBreak) 41 | 42 | 43 | class UnPrintable(Layout.Box): 44 | """ 45 | Defines content as being unprintable and therefore should be hidden from printing 46 | """ 47 | __slots__ = () 48 | 49 | def _create(self, id=None, name=None, parent=None, **kwargs): 50 | Layout.Box._create(self, id=id, name=name, parent=parent) 51 | 52 | self.addClass('hidePrint') 53 | 54 | Factory.addProduct(UnPrintable) 55 | -------------------------------------------------------------------------------- /thedom/resources.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Resources.py 3 | 4 | Contains elements that enable use of external resources such as CSS files 5 | 6 | Copyright (C) 2015 Timothy Edmund Crosley 7 | 8 | This program is free software; you can redistribute it and/or 9 | modify it under the terms of the GNU General Public License 10 | as published by the Free Software Foundation; either version 2 11 | of the License, or (at your option) any later version. 12 | 13 | This program is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with this program; if not, write to the Free Software 20 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 21 | ''' 22 | 23 | import types 24 | 25 | from . import DOM, Base, ClientSide, Factory 26 | from .DOM import H2, Link, Script 27 | from .MethodUtils import CallBack 28 | from .MultiplePythonSupport import * 29 | 30 | Factory = Factory.Factory("Resources") 31 | 32 | 33 | class ResourceFile(Base.Node): 34 | """ 35 | Enables you to add resource files (javascript, css, etc..) to a page 36 | """ 37 | __slots__ = ('resourceFile', 'fileName', 'resourceFile', 'resourceType') 38 | properties = Base.Node.properties.copy() 39 | properties['file'] = {'action':'setFile'} 40 | properties['media'] = {'action':'attribute'} 41 | displayable = False 42 | 43 | def _create(self, id=None, name=None, parent=None, **kwargs): 44 | Base.Node._create(self, id, name) 45 | self.resourceFile = self.add(Base.TextNode()) 46 | self.setFile("") 47 | 48 | def shown(self): 49 | """ 50 | Resource files are never visible 51 | """ 52 | return False 53 | 54 | def setFile(self, fileName): 55 | """ 56 | Sets the location of the resource file, and determines the resource type from that: 57 | fileName - the disk name of the file. 58 | """ 59 | self.fileName = fileName 60 | 61 | extension = fileName.split("?")[0][-3:] 62 | if ":" in fileName: 63 | rel, href = fileName.split(":") 64 | resource = Link() 65 | resource.setProperties((('rel', rel), ('src', href))) 66 | self.resourceType = rel 67 | elif extension == ".js": 68 | resource = Script() 69 | resource.setProperties((('src', fileName), )) 70 | self.resourceType = "javascript" 71 | elif extension == "css": 72 | resource = Link() 73 | resource.setProperties((('rel', 'stylesheet'), ('type','text/css'), ('href', fileName))) 74 | self.resourceType = "css" 75 | elif extension == "png": 76 | resource = Link() 77 | resource.setProperties((('rel', 'icon'), ('type','image/png'), ('href', fileName))) 78 | self.resourceType = "favicon" 79 | else: 80 | resource = H2() 81 | resource.add(Base.TextNode("Invalid Resource: %s" % fileName)) 82 | self.resourceType = None 83 | 84 | self.resourceFile = self.resourceFile.replaceWith(resource) 85 | 86 | Factory.addProduct(ResourceFile) 87 | 88 | 89 | class ScriptContainer(DOM.Script): 90 | """ 91 | All scripts should be stored in a Script Box object 92 | """ 93 | __slots__ = ('_scripts', 'usedObjects') 94 | displayable = False 95 | properties = DOM.Script.properties.copy() 96 | properties['script'] = {'action':'addScript'} 97 | 98 | def _create(self, id=None, name=None, parent=None, **kwargs): 99 | Base.Node._create(self) 100 | self.attributes['language'] = 'javascript' 101 | self.attributes['type'] = 'text/javascript' 102 | self._scripts = [] 103 | 104 | def content(self, formatted=False, *args, **kwargs): 105 | """ 106 | Overrides the base content method to return the javascript associated with the scriptcontainer 107 | """ 108 | scriptContent = ";".join([str(script) for script in self.scripts()]) 109 | 110 | return scriptContent 111 | 112 | def addScript(self, script): 113 | """ 114 | Adds a script to the container 115 | """ 116 | if not script in self._scripts: 117 | self._scripts.append(script) 118 | 119 | def removeScript(self, script): 120 | """ 121 | Removes a script that has been passed into the container 122 | """ 123 | if script in self._scripts: 124 | self._scripts.remove(script) 125 | 126 | def shown(self): 127 | """ 128 | Script containers are never visible 129 | """ 130 | return False 131 | 132 | def scripts(self): 133 | """ 134 | Returns a list of all passed in scripts 135 | """ 136 | return self._scripts 137 | 138 | Factory.addProduct(ScriptContainer) 139 | -------------------------------------------------------------------------------- /thedom/string_utils.py: -------------------------------------------------------------------------------- 1 | ''' 2 | StringUtils.py 3 | 4 | Provides methods that ease complex python string operations 5 | 6 | Copyright (C) 2015 Timothy Edmund Crosley 7 | 8 | This program is free software; you can redistribute it and/or 9 | modify it under the terms of the GNU General Public License 10 | as published by the Free Software Foundation; either version 2 11 | of the License, or (at your option) any later version. 12 | 13 | This program is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with this program; if not, write to the Free Software 20 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 21 | ''' 22 | 23 | import random 24 | import re 25 | import string 26 | import types 27 | from urllib import urlencode 28 | 29 | from . import ClientSide 30 | from .MultiplePythonSupport import * 31 | 32 | INVALID_CONTROL_CHARACTERS = [ 33 | chr(0x00), 34 | chr(0x01), 35 | chr(0x02), 36 | chr(0x03), 37 | chr(0x04), 38 | chr(0x05), 39 | chr(0x06), 40 | chr(0x07), 41 | chr(0x08), 42 | chr(0x0b), 43 | chr(0x0c), 44 | chr(0x0e), 45 | chr(0x0f), 46 | chr(0x10), 47 | chr(0x11), 48 | chr(0x12), 49 | chr(0x13), 50 | chr(0x14), 51 | chr(0x15), 52 | chr(0x16), 53 | chr(0x17), 54 | chr(0x18), 55 | chr(0x19), 56 | chr(0x1a), 57 | chr(0x1b), 58 | chr(0x1c), 59 | chr(0x1d), 60 | chr(0x1e), 61 | chr(0x1f) 62 | ] 63 | 64 | def patternSplit(text, pattern): 65 | """ 66 | Splits a string into a list of strings at each point it matches a pattern: 67 | test - the text to match against 68 | pattern - a regex pattern to match against 69 | """ 70 | matchObj = re.compile(pattern).split(text) 71 | tokenList = [] 72 | for element in matchObj: 73 | if element != "": 74 | tokenList.append(element.upper()) 75 | return tokenList 76 | 77 | 78 | def removeAlphas(value): 79 | """ 80 | Returns a string removed of any extra formatting in the string or combined numbers and characters. 81 | """ 82 | newValue = '' 83 | for part in value: 84 | if part.isdigit(): 85 | newValue += part 86 | return newValue 87 | 88 | def interpretFromString(value): 89 | """ 90 | returns the python equivalent value from an xml string (such as an attribute value): 91 | value - the html value to interpret 92 | """ 93 | lowerCaseValue = value.lower() 94 | if lowerCaseValue == "true": 95 | return True 96 | elif lowerCaseValue == "false": 97 | return False 98 | elif lowerCaseValue == "none": 99 | return None 100 | 101 | return value 102 | 103 | def listReplace(inString, listOfItems, replacement): 104 | """ 105 | Replaces instaces of items withing listOfItems with replacement: 106 | inString - the string to do replacements on 107 | listOfItems - a list of strings to replace 108 | replacement - what to replace it with (or a list of replacements the same lenght as the list of items) 109 | """ 110 | isStringReplace = type(replacement) in (str, unicode) 111 | for item in listOfItems: 112 | if isStringReplace: 113 | inString = inString.replace(item, replacement) 114 | else: 115 | inString = inString.replace(item, replacement[listOfItems.index(item)]) 116 | 117 | return inString 118 | 119 | def removeDelimiters(inString, replacement=""): 120 | """ 121 | Removes the specified delimiters from the inString. 122 | """ 123 | return listReplace(inString, ['.', ',', '+', '-', '/', '\\'], replacement) 124 | 125 | def stripControlChars(text, fromFront=True, fromBack=True): 126 | """ 127 | Removes control characters from supplied text. 128 | """ 129 | if not text: 130 | return '' 131 | 132 | invalidChars = ''.join(INVALID_CONTROL_CHARACTERS) 133 | if fromFront and fromBack: 134 | return text.strip(invalidChars) 135 | elif fromFront: 136 | return text.lstrip(invalidChars) 137 | elif fromBack: 138 | return text.rstrip(invalidChars) 139 | else: 140 | return text 141 | 142 | def findIndexes(text, subString): 143 | """ 144 | Returns a set of all indexes of subString in text. 145 | """ 146 | indexes = set() 147 | lastFoundIndex = 0 148 | while True: 149 | foundIndex = text.find(subString, lastFoundIndex) 150 | if foundIndex == -1: 151 | break 152 | indexes.add(foundIndex) 153 | lastFoundIndex = foundIndex + 1 154 | return indexes 155 | 156 | def encodeAnything(anything, encoding='utf8'): 157 | """ 158 | Returns any data that is passed in encoded into the specified encoding or throws an exception. 159 | """ 160 | if type(anything) in (str, unicode): 161 | return unicode(anything).encode(encoding) 162 | if isinstance(anything, list): 163 | for index, thing in enumerate(anything): 164 | anything[index] = encodeAnything(thing, encoding) 165 | return anything 166 | if isinstance(anything, dict): 167 | for key, thing in iteritems(anything): 168 | anything[key] = encodeAnything(thing, encoding) 169 | return anything 170 | if type(anything) == tuple: 171 | return tuple([encodeAnything(thing) for thing in anything]) 172 | 173 | return anything 174 | 175 | def generateRandomKey(size=20, chars=string.ascii_uppercase + string.digits): 176 | """ 177 | Generates a random key of a certain length, based on a given pool of characters 178 | size - the lenght of the random key 179 | chars - the pool of characters from which to pool each item 180 | """ 181 | return ''.join(random.choice(chars) for x in range(size)) 182 | 183 | def everyDirAndSub(directory): 184 | """ 185 | Splits a directory to get every directory and subdirectory as a list. 186 | """ 187 | ret = [] 188 | idx = 0 189 | while True: 190 | try: 191 | idx = directory.index('/', idx + 1) 192 | except: 193 | break 194 | ret += [directory[:idx]] 195 | ret += [directory] 196 | return ret 197 | 198 | def scriptURL(argumentDictionary): 199 | """ 200 | Encodes a dictionary into a URL, while allowing scripts to be ran to form the URL client side 201 | """ 202 | scriptParams = [] 203 | for argumentName, argumentValue in argumentDictionary.items(): 204 | if isinstance(argumentValue, ClientSide.Script): 205 | argumentDictionary.pop(argumentName) 206 | scriptParams.append('%s=" + %s' % (argumentName, argumentValue.claim())) 207 | if not scriptParams: 208 | return urlencode(argumentDictionary) 209 | elif argumentDictionary: 210 | scriptParams += urlencode(argumentDictionary) 211 | 212 | urlScript = ' + "'.join(scriptParams) 213 | if not argumentDictionary: 214 | urlScript = '"' + urlScript 215 | 216 | return ClientSide.Script(urlScript) 217 | -------------------------------------------------------------------------------- /thedom/types.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Types.py 3 | 4 | Defines Node DataTypes, mainly overrides of built-ins with guaranteed HTML aware string representations 5 | and safety markings. 6 | 7 | Copyright (C) 2015 Timothy Edmund Crosley 8 | 9 | This program is free software; you can redistribute it and/or 10 | modify it under the terms of the GNU General Public License 11 | as published by the Free Software Foundation; either version 2 12 | of the License, or (at your option) any later version. 13 | 14 | This program is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with this program; if not, write to the Free Software 21 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 22 | ''' 23 | 24 | 25 | import cgi 26 | 27 | from .MultiplePythonSupport import * 28 | 29 | 30 | class WebDataType(object): 31 | __slots__ = () 32 | 33 | def __unicode__(self): 34 | return cgi.escape(unicode(self)) 35 | 36 | def __str__(self): 37 | return self.__unicode__() 38 | 39 | class SafeDataType(WebDataType): 40 | __slots__ = () 41 | 42 | def __unicode__(self): 43 | return unicode(self) 44 | 45 | def __str__(self): 46 | return self.__unicode__() 47 | 48 | class Unsafe(unicode, WebDataType): 49 | """ 50 | Explicitly marks a string as being unsafe so it will be cgi escaped 51 | """ 52 | def __unicode__(self): 53 | return cgi.escape(self) 54 | 55 | def __str__(self): 56 | return self.__unicode__() 57 | 58 | class Safe(unicode, SafeDataType): 59 | """ 60 | Explicitly marks a string as safe so it will not be cgi escaped 61 | """ 62 | def __unicode__(self): 63 | return self 64 | 65 | def __str__(self): 66 | return self.__unicode__() 67 | 68 | class Set(set, WebDataType): 69 | __slots__ = () 70 | 71 | def __unicode__(self): 72 | return cgi.escape(" ".join(self)) 73 | 74 | def __str__(self): 75 | return self.__unicode__() 76 | 77 | 78 | class StyleDict(dict, WebDataType): 79 | __slots__ = () 80 | 81 | def __unicode__(self): 82 | return cgi.escape(";".join([unicode(dictKey) + ':' + unicode(dictValue) for dictKey, dictValue in iteritems(self)])) 83 | 84 | def __str__(self): 85 | return self.__unicode__() 86 | 87 | @classmethod 88 | def fromString(cls, styleString): 89 | styleDict = cls() 90 | 91 | styleDefinitions = styleString.split(';') 92 | for definition in styleDefinitions: 93 | if definition: 94 | name, value = definition.split(':') 95 | styleDict[name.strip()] = value.strip() 96 | 97 | return styleDict 98 | 99 | 100 | class Scripts(list, SafeDataType): 101 | __slots__ = () 102 | 103 | def __unicode__(self): 104 | return ";".join([unicode(item) for item in self]) 105 | 106 | def __str__(self): 107 | return self.__unicode__() 108 | 109 | 110 | class Bool(SafeDataType): 111 | __slots__ = ('boolean', ) 112 | 113 | def __init__(self, boolean): 114 | self.boolean = boolean 115 | 116 | def __nonzero__(self): 117 | return self.boolean 118 | 119 | def __unicode__(self): 120 | return cgi.escape(unicode(self.boolean).lower()) 121 | 122 | def __str__(self): 123 | return self.__unicode__() 124 | -------------------------------------------------------------------------------- /thedom/ui_template.py: -------------------------------------------------------------------------------- 1 | ''' 2 | UITemplate.py 3 | 4 | Classes and methods that aid in creating python dictionaries from XML or SHPAML templates 5 | 6 | Copyright (C) 2015 Timothy Edmund Crosley 7 | 8 | This program is free software; you can redistribute it and/or 9 | modify it under the terms of the GNU General Public License 10 | as published by the Free Software Foundation; either version 2 11 | of the License, or (at your option) any later version. 12 | 13 | This program is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with this program; if not, write to the Free Software 20 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 21 | ''' 22 | 23 | from xml.dom import minidom 24 | 25 | from . import shpaml 26 | from .Base import Node 27 | from .MultiplePythonSupport import * 28 | from .StringUtils import interpretFromString 29 | 30 | # Supported format types 31 | XML = 0 32 | SHPAML = 1 33 | 34 | class Template(object): 35 | """ 36 | A very memory efficient representation of a user interface template 37 | """ 38 | __slots__ = ('create', 'accessor', 'id', 'name', 'childElements', 'properties') 39 | 40 | class Rendered(Node): 41 | pass 42 | 43 | def __init__(self, create, accessor="", id="", name="", childElements=None, properties=()): 44 | self.create = create 45 | self.accessor = accessor 46 | self.id = id 47 | self.name = name 48 | self.childElements = childElements 49 | self.properties = properties 50 | 51 | def __getstate__(self): 52 | return (self.create, self.accessor, self.id, self.name, self.childElements, self.properties) 53 | 54 | def __setstate__(self, state): 55 | (self.create, self.accessor, self.id, self.name, self.childElements, self.properties) = state 56 | 57 | def __eq__(self, other): 58 | if (self.create != other.create or self.accessor != other.accessor or self.id != other.id or 59 | self.name != other.name or self.properties != other.properties): 60 | return False 61 | 62 | if self.childElements: 63 | if len(self.childElements) != len(other.childElements): 64 | return False 65 | for child, otherChild in zip(self.childElements, other.childElements): 66 | if not child.__eq__(otherChild): 67 | return False 68 | elif other.childElements: 69 | return False 70 | 71 | return True 72 | 73 | def build(self, factory): 74 | templateElement = self.Rendered() 75 | 76 | accessors = {} 77 | instance = factory.buildFromTemplate(self, accessors=accessors, parent=templateElement) 78 | for accessor, element in iteritems(accessors): 79 | if hasattr(templateElement, accessor): 80 | raise ValueError("The accessor name or id of the element has to be unique and can not be the " 81 | "same as a base webelement attribute." 82 | "Failed on adding element with id or accessor '%s'." % accessor) 83 | 84 | templateElement.__setattr__(accessor, element) 85 | 86 | templateElement += instance 87 | return templateElement 88 | 89 | 90 | def fromFile(templateFile, formatType=SHPAML): 91 | """ 92 | Returns a parsable dictionary representation of the interface: 93 | templateFile - a file containing an xml representation of the interface 94 | """ 95 | if formatType == XML: 96 | return __createTemplateFromXML(minidom.parse(templateFile).childNodes[0]) 97 | elif formatType == SHPAML: 98 | with open(templateFile) as openFile: 99 | return fromSHPAML(openFile.read()) 100 | 101 | def fromXML(xml): 102 | """ 103 | Returns a parsable dictionary representation of the interface: 104 | xml - a string containing an xml representation of the interface 105 | """ 106 | xmlStructure = minidom.parseString(xml) 107 | return __createTemplateFromXML(xmlStructure.childNodes[0]) 108 | 109 | def fromSHPAML(shpamlTemplate): 110 | """ 111 | Returns a parsable dictionary representation of the interface: 112 | shpaml - a string containing a shpaml representation of the interface 113 | """ 114 | xmlStructure = minidom.parseString(shpaml.convert_text(shpamlTemplate)) 115 | return __createTemplateFromXML(xmlStructure.childNodes[0]) 116 | 117 | def __createTemplateFromXML(xml): 118 | """ 119 | Parses an xml string converting it to an easily parse-able python template representation: 120 | xml - a string containing an xml representation of the interface 121 | """ 122 | if isinstance(xml, minidom.Text): 123 | return xml.nodeValue.strip() 124 | 125 | (create, attributes, children) = (xml.tagName, xml.attributes, xml.childNodes) 126 | accessor = attributes.get('accessor', "") 127 | id = attributes.get('id', "") 128 | name = attributes.get('name', "") 129 | if accessor: 130 | accessor = accessor.value 131 | attributes.removeNamedItem('accessor') 132 | if id: 133 | id = id.value 134 | attributes.removeNamedItem('id') 135 | if name: 136 | name = name.value 137 | attributes.removeNamedItem('name') 138 | 139 | properties = tuple(((attribute[0], interpretFromString(attribute[1])) for attribute in attributes.items())) 140 | if children: 141 | childNodes = (__createTemplateFromXML(node) for node in children if 142 | node.__class__ in (minidom.Element, minidom.Text)) 143 | childElements = tuple(child for child in childNodes if child) 144 | else: 145 | childElements = None 146 | 147 | return Template(create, accessor, id, name, childElements, properties) 148 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist=py33, py34 3 | 4 | [testenv] 5 | deps=-rrequirements/development.txt 6 | commands=py.test --cov thedom tests 7 | coverage report 8 | --------------------------------------------------------------------------------