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 |
--------------------------------------------------------------------------------