')
50 |
51 | def cellContents(self, _rowIndex, _colIndex, value):
52 | """Hook for subclasses to customize the contents of a cell.
53 |
54 | Based on any criteria (including location).
55 | """
56 | return value
57 |
--------------------------------------------------------------------------------
/webware/Admin/EditFile.py:
--------------------------------------------------------------------------------
1 | from Page import Page
2 |
3 | # Set this to True if you want to pass additional information.
4 | # For security reasons, this has been disabled by default.
5 | appInfo = False
6 |
7 |
8 | class EditFile(Page):
9 | """Helper for the feature provided by the IncludeEditLink setting."""
10 |
11 | def writeInfo(self, key, value):
12 | self.writeln(f'{key}: {value}')
13 |
14 | def writeHTML(self):
15 | res = self.response()
16 | header = res.setHeader
17 | info = self.writeInfo
18 | req = self.request()
19 | env = req.environ()
20 | field = req.field
21 |
22 | header('Content-Type', 'application/x-webware-edit-file')
23 | header('Content-Disposition', 'inline; filename="Webware.EditFile"')
24 |
25 | # Basic information for editing the file:
26 | info('Filename', field('filename'))
27 | info('Line', field('line'))
28 |
29 | # Additional information about the hostname:
30 | info('Hostname', env.get('HTTP_HOST', 'localhost'))
31 |
32 | if appInfo:
33 | # Additional information about this Webware installation:
34 | app = self.application()
35 | info('ServerSidePath', app.serverSidePath())
36 | info('WebwarePath', app.webwarePath())
37 |
--------------------------------------------------------------------------------
/webware/Admin/Errors.py:
--------------------------------------------------------------------------------
1 | from os import sep
2 |
3 | from WebUtils.Funcs import urlEncode
4 |
5 | from .DumpCSV import DumpCSV
6 |
7 |
8 | class Errors(DumpCSV):
9 |
10 | def filename(self):
11 | return self.application().setting('ErrorLogFilename')
12 |
13 | def cellContents(self, _rowIndex, colIndex, value):
14 | """Hook for subclasses to customize the contents of a cell.
15 |
16 | Based on any criteria (including location).
17 | """
18 | if self._headings[colIndex] in ('pathname', 'error report filename'):
19 | path = self.application().serverSidePath()
20 | if value.startswith(path):
21 | value = value[len(path):]
22 | if value.startswith(sep):
23 | value = value[len(sep):]
24 | link = f'View?filename={urlEncode(value)}'
25 | value = value.replace(sep, sep + '')
26 | value = f'{value}'
27 | else:
28 | value = value.replace(sep, sep + '')
29 | return value
30 | if self._headings[colIndex] == 'time':
31 | return f'{value}'
32 | return self.htmlEncode(value)
33 |
--------------------------------------------------------------------------------
/webware/Admin/LoginPage.py:
--------------------------------------------------------------------------------
1 | from MiscUtils.Funcs import uniqueId
2 |
3 | from .AdminPage import AdminPage
4 |
5 |
6 | class LoginPage(AdminPage):
7 | """The log-in screen for the admin pages."""
8 |
9 | def writeContent(self):
10 | if self.loginDisabled():
11 | self.write(self.loginDisabled())
12 | return
13 | self.writeln(
14 | '
'
15 | '
')
16 | extra = self.request().field('extra', None)
17 | if extra:
18 | self.writeln(f'
{self.htmlEncode(extra)}
')
19 | self.writeln('''
Please log in to view Administration Pages.
20 | The username is admin. The password can be set in the
21 | Application.config file in the Configs directory.
')
17 | for plugIn in plugIns.values():
18 | name, path = plugIn.name(), plugIn.path()
19 | ver = plugIn.properties()['versionString']
20 | wr(f'
{name}
'
21 | f'
{ver}
'
22 | f'
{path}
')
23 | wr('
')
24 | else:
25 | wr('
No Plug-ins found.
')
26 |
--------------------------------------------------------------------------------
/webware/Admin/View.py:
--------------------------------------------------------------------------------
1 | from os.path import exists, splitext
2 |
3 | from .AdminSecurity import AdminSecurity
4 |
5 |
6 | class View(AdminSecurity):
7 | """View a text or html file.
8 |
9 | The Admin View servlet loads any text or html file
10 | in your application working directory on the Webware server
11 | and displays it in the browser for your viewing pleasure.
12 | """
13 |
14 | def defaultAction(self):
15 | self._data = self._type = None
16 | self.writeHTML()
17 | if self._data and self._type:
18 | try:
19 | response = self.response()
20 | response.reset()
21 | response.setHeader('Content-Type', self._type)
22 | self.write(self._data)
23 | except Exception:
24 | self.writeError('File cannot be viewed!')
25 |
26 | def writeError(self, message):
27 | self.writeln(f'
Error
{message}
')
28 |
29 | def writeContent(self):
30 | filename = self.request().field('filename', None)
31 | if not filename:
32 | self.writeError('No filename given to view!')
33 | return
34 | filename = self.application().serverSidePath(filename)
35 | if not exists(filename):
36 | self.writeError(
37 | f'The requested file {filename!r} does not exist'
38 | ' in the server side directory.')
39 | return
40 | self._type = 'text/' + (
41 | 'html' if splitext(filename)[1] in ('.htm', '.html') else 'plain')
42 | try:
43 | with open(filename, encoding='utf-8') as f:
44 | self._data = f.read()
45 | except Exception:
46 | self.writeError(f'The requested file {filename!r} cannot be read.')
47 |
--------------------------------------------------------------------------------
/webware/Admin/__init__.py:
--------------------------------------------------------------------------------
1 | """Admin context"""
2 |
--------------------------------------------------------------------------------
/webware/ConfigurableForServerSidePath.py:
--------------------------------------------------------------------------------
1 | import os.path
2 |
3 | from MiscUtils.Configurable import Configurable, NoDefault
4 |
5 |
6 | class ConfigurableForServerSidePath(Configurable):
7 | """Configuration file functionality incorporating a server side path.
8 |
9 | This is a version of `MiscUtils.Configurable.Configurable` that provides
10 | a customized `setting` method for classes which have a `serverSidePath`
11 | method. If a setting's name ends with ``Filename`` or ``Dir``, its value
12 | is passed through `serverSidePath` before being returned.
13 |
14 | In other words, relative filenames and directory names are expanded with
15 | the location of the object, *not* the current directory.
16 |
17 | Application is a prominent class that uses this mix-in. Any class that
18 | has a `serverSidePath` method and a `Configurable` base class, should
19 | inherit this class instead.
20 |
21 | This is used for `MakeAppWorkDir`, which changes the `serverSidePath`.
22 | """
23 |
24 | def setting(self, name, default=NoDefault):
25 | """Return setting, using the server side path when indicated.
26 |
27 | Returns the setting, filtered by `self.serverSidePath()`,
28 | if the name ends with ``Filename`` or ``Dir``.
29 | """
30 | value = Configurable.setting(self, name, default)
31 | if name.endswith(('Dir', 'Filename')) and (
32 | value or name.endswith('Dir')):
33 | if name.endswith('LogFilename') and '/' not in value:
34 | value = os.path.join(
35 | Configurable.setting(self, 'LogDir'), value)
36 | value = self.serverSidePath(value) # pylint: disable=no-member
37 | return value
38 |
--------------------------------------------------------------------------------
/webware/Examples/AjaxSuggest.py:
--------------------------------------------------------------------------------
1 | #
2 | # Ajax "Suggest" Example
3 | #
4 | # Written by Robert Forkel based on
5 | # https://webwareforpython.github.io/w4py-olde-wiki/ajaxinwebware.html
6 | # and http://www.dynamicajax.com/fr/AJAX_Suggest_Tutorial-271_290_312.html,
7 | # with minor changes made by Christoph Zwerschke.
8 | #
9 |
10 | from random import randint
11 |
12 | from .AjaxPage import AjaxPage
13 |
14 | maxSuggestions = 10
15 |
16 | # Create some random "magic words":
17 | maxLetters, maxWords = 5, 5000
18 | magicWords = [''.join(chr(randint(97, 122)) for j in range(maxLetters))
19 | for i in range(maxWords)]
20 |
21 |
22 | class AjaxSuggest(AjaxPage):
23 |
24 | _clientPolling = None # we have no long-running queries
25 |
26 | def writeJavaScript(self):
27 | AjaxPage.writeJavaScript(self)
28 | self.writeln(
29 | '')
30 |
31 | def writeStyleSheet(self):
32 | AjaxPage.writeStyleSheet(self)
33 | self.writeln(
34 | '')
35 |
36 | def htBodyArgs(self):
37 | return AjaxPage.htBodyArgs(self) + ' onload="initPage();"'
38 |
39 | def writeContent(self):
40 | self.writeln('
You've been here
17 | {count:d}
18 | time{'' if count == 1 else 's'}.
19 |
This page records your visits using a session object.
20 | Every time you reload
21 | or revisit this page, the counter will increase.
22 | If you close your browser or force the session to
23 | expire, then your session will end
24 | and you will see the counter go back to 1 on your next visit.
28 | ''')
36 |
--------------------------------------------------------------------------------
/webware/Examples/FileUpload.py:
--------------------------------------------------------------------------------
1 | from .ExamplePage import ExamplePage
2 |
3 |
4 | class FileUpload(ExamplePage):
5 | """This servlet shows how to handle uploaded files.
6 |
7 | The process is fairly self-explanatory. You use a form like the one below
8 | in the writeContent method. When the form is uploaded, the request field
9 | with the name you gave to the file selector form item will be an instance
10 | of the FieldStorage class from the WebUtils.FieldStorage module. The key
11 | attributes of this class are shown in the example below. The most important
12 | things are filename, which gives the name of the file that was uploaded,
13 | and file, which is an open file handle to the uploaded file. The uploaded
14 | file is temporarily stored in a temp file created by the standard module.
15 | You'll need to do something with the data in this file. The temp file will
16 | be automatically deleted. If you want to save the data in the uploaded file
17 | read it out and write it to a new file, database, whatever.
18 | """
19 |
20 | def title(self):
21 | return "File Upload Example"
22 |
23 | def writeContent(self):
24 | self.writeln("
This is the Forward servlet speaking. I am now"
11 | " going to include the output of the IncludeMe servlet"
12 | " via Application's includeURL() method:
The following table shows the values for various Python"
10 | " expressions, all of which are related to introspection."
11 | " That is to say, all the expressions examine the environment such"
12 | " as the object, the object's class, the module and so on.
')
29 |
30 | def list(self, codeString):
31 | value = eval(codeString)
32 | if not isinstance(value, (list, tuple)):
33 | value = 'not a list or a tuple'
34 | self.pair(codeString, value)
35 |
--------------------------------------------------------------------------------
/webware/Examples/JSONRPCClient.py:
--------------------------------------------------------------------------------
1 | """JSON-RPC demo client contributed by Christoph Zwerschke"""
2 |
3 | from .ExamplePage import ExamplePage
4 |
5 |
6 | class JSONRPCClient(ExamplePage):
7 | """Demo client for using the JSON-RPC example."""
8 |
9 | def writeJavaScript(self):
10 | ExamplePage.writeJavaScript(self)
11 | self.write('''\
12 |
13 |
23 | ''')
24 |
25 | def writeContent(self):
26 | self.write('''\
27 |
JSON-RPC Example
28 |
This example shows how you can call methods
29 | of a JSON-RPC servlet
30 | built with Webware for Python from your web browser
31 | via JavaScript (which has to be activated to run this demo).
32 |
35 |
The example uses a JSON-RPC JavaScript client
36 | based on Jan-Klaas' "JavaScript o lait" library.
37 |
Type in any example text to be used as input parameter,
38 | choose one of the available methods to be invoked by the
39 | example servlet and press the button to display the result.
')
19 | request = self.request()
20 | extra = request.field('extra', None)
21 | if (not extra and request.isSessionExpired()
22 | and not request.hasField('logout')):
23 | extra = 'You have been automatically logged out due to inactivity.'
24 | if extra:
25 | self.writeln(
26 | f'
{self.htmlEncode(extra)}
')
27 | if self.session().hasValue('loginId'):
28 | loginId = self.session().value('loginId')
29 | else:
30 | # Create a "unique" login id and put it in the form as well as
31 | # in the session. Login will only be allowed if they match.
32 | loginId = uniqueId(self)
33 | self.session().setValue('loginId', loginId)
34 | action = request.field('action', '')
35 | if action:
36 | action = f' action="{action}"'
37 | self.writeln(f'''
Please log in to view the example.
38 | The username and password is alice or bob.
This page records your visits using a session object.'
22 | f' Every time you {reload} or {revisit} this page,'
23 | ' the counter will increase. If you close your browser,'
24 | ' then your session will end and you will see the counter'
25 | ' go back to 1 on your next visit.
')
26 | self.writeln(f'
Try hitting {reload} now.
')
27 | user = self.loggedInUser()
28 | if user:
29 | self.writeln(
30 | f'
')
9 |
--------------------------------------------------------------------------------
/webware/Examples/View.py:
--------------------------------------------------------------------------------
1 | from os import sep
2 |
3 | from .ExamplePage import ExamplePage
4 |
5 |
6 | class View(ExamplePage):
7 | """View the source of a Webware servlet.
8 |
9 | For each Webware example, you will see a sidebar with various menu items,
10 | one of which is "View source of example". This link points to the
11 | View servlet and passes the filename of the current servlet. The View
12 | servlet then loads that file's source code and displays it in the browser
13 | for your viewing pleasure.
14 |
15 | Note that if the View servlet isn't passed a filename,
16 | it prints the View's docstring which you are reading right now.
17 | """
18 |
19 | def writeContent(self):
20 | req = self.request()
21 | if req.hasField('filename'):
22 | trans = self.transaction()
23 | filename = req.field('filename')
24 | if sep in filename:
25 | self.write(
26 | '
Error
Cannot request a file'
27 | f' outside of this directory {filename!r}
Along the side of this page you will see various links
13 | that will take you to:
14 |
15 |
The different Webware examples.
16 |
The source code of the current example.
17 |
Whatever contexts have been configured.
18 | Each context represents a distinct set of web pages,
19 | usually given a descriptive name.
20 |
External sites, such as the Webware home page.
21 |
22 |
The Admin context is particularly
23 | interesting because it takes you to the administrative pages
24 | for the Webware application where you can review logs,
25 | configuration, plug-ins, etc.
Note:
32 | extraURLPath information was found on the URL,
33 | and a servlet was not found to process it.
34 | Processing has been delegated to this servlet.
35 | ''')
36 | wr('
')
37 | wr(f'
serverSidePath: {req.serverSidePath()}
')
38 | wr(f'
extraURLPath: {extraURLPath}
')
39 | wr('
')
40 |
--------------------------------------------------------------------------------
/webware/Examples/XMLRPCExample.py:
--------------------------------------------------------------------------------
1 | from functools import reduce
2 | from operator import truediv
3 |
4 | from webware.XMLRPCServlet import XMLRPCServlet
5 |
6 |
7 | class XMLRPCExample(XMLRPCServlet):
8 | """Example XML-RPC servlet.
9 |
10 | To try it out, use something like the following:
11 |
12 | >>> from xmlrpc.client import ServerProxy as Server
13 | >>> server = Server('http://localhost:8080/Examples/XMLRPCExample')
14 | >>> server.multiply(10,20)
15 | 200
16 | >>> server.add(10,20)
17 | 30
18 |
19 | You'll get an exception if you try to call divide, because that
20 | method is not listed in exposedMethods.
21 | """
22 |
23 | def exposedMethods(self):
24 | return ['multiply', 'add']
25 |
26 | def multiply(self, x, y):
27 | return x * y
28 |
29 | def add(self, x, y):
30 | return x + y
31 |
32 | def divide(self, *args):
33 | return reduce(truediv, args)
34 |
--------------------------------------------------------------------------------
/webware/Examples/__init__.py:
--------------------------------------------------------------------------------
1 | """Main Examples context"""
2 |
--------------------------------------------------------------------------------
/webware/Examples/ajaxpoll.js:
--------------------------------------------------------------------------------
1 | /*
2 | Extended Ajax JavaScript functions used by AjaxPage.
3 |
4 | Implements a periodic polling mechanism to prevent server timeouts
5 | and to allow pushing commands from the server to the client.
6 |
7 | Written by John Dickinson based on ideas from
8 | Apple Developer Connection and DivMod Nevow.
9 | Some changes made by Christoph Zwerschke.
10 | */
11 |
12 | var dying = false;
13 | var poll_requester = getRequester();
14 |
15 | function openPollConnection()
16 | {
17 | if (poll_requester)
18 | {
19 | var req = poll_requester;
20 | req.onreadystatechange = function() {
21 | var wait = 3 + Math.random() * 5; // 3 - 8 seconds
22 | if (req.readyState == 4) {
23 | if (req.status == 200) {
24 | try {
25 | eval(req.responseText);
26 | req.abort();
27 | } catch(e) { } // ignore errors
28 | if (!dying) {
29 | // reopen the response connection after a wait period
30 | setTimeout("openPollConnection()", wait*1000);
31 | }
32 | }
33 | }
34 | }
35 | var url = request_url + 'Poll&_req_=' + ++request_id;
36 | req.open("GET", url, true);
37 | req.send(null);
38 | }
39 | }
40 |
41 | function shutdown()
42 | {
43 | if (poll_requester) {
44 | poll_requester.abort();
45 | }
46 | dying = true;
47 | }
48 |
49 | if (window.addEventListener)
50 | window.addEventListener("beforeunload", shutdown, false);
51 | else if (window.attachEvent) // MSIE
52 | window.attachEvent("onbeforeunload", shutdown);
53 |
54 | // Open initial connection back to server:
55 | openPollConnection();
56 |
--------------------------------------------------------------------------------
/webware/Examples/ajaxsuggest.css:
--------------------------------------------------------------------------------
1 | /* Style sheet for the AjaxSuggest example. */
2 |
3 | .in_red {
4 | color: #a33;
5 | }
6 |
7 | .suggest_button_normal {
8 | color: #000;
9 | background-color: #ddd;
10 | text-align: center;
11 | font-size: smaller;
12 | padding: 1px 4px;
13 | border: 2px outset #666;
14 | }
15 |
16 | .suggest_button_over {
17 | color: #ddd;
18 | background-color: #36c;
19 | text-align: center;
20 | font-size: smaller;
21 | padding: 1px 4px;
22 | border: 2px outset #666;
23 | }
24 |
25 | .suggest_link_normal {
26 | color: #000;
27 | background-color: #fff;
28 | padding: 2px 4px;
29 | }
30 |
31 | .suggest_link_over {
32 | color: #fff;
33 | background-color: #36c;
34 | padding: 2px 4px;
35 | }
36 |
37 | #query {
38 | padding: 2px 4px;
39 | border: 2px solid #000;
40 | }
41 |
42 | #suggestions {
43 | cursor: pointer;
44 | position: absolute;
45 | text-align: left;
46 | border: 1px solid #666;
47 | }
48 |
49 | .show {
50 | display: block;
51 | }
52 |
53 | .hide {
54 | display: none;
55 | }
56 |
--------------------------------------------------------------------------------
/webware/Examples/ajaxsuggest.js:
--------------------------------------------------------------------------------
1 | /*
2 | JavaScript for the AjaxSuggest example.
3 |
4 | Provides all the client-side heavy lifting required to get Ajax functionality into a web page.
5 |
6 | Coded by Ryan Smith (www.dynamicajax.com/fr/AJAX_Suggest_Tutorial-271_290_312.html).
7 | Adapted for Webware by Robert Forkel.
8 | */
9 |
10 | // Function that is called after the page has been loaded:
11 | function initPage() {
12 | document.getElementById('query').focus();
13 | }
14 |
15 | // Function to be associated with input control (initiates the Ajax request):
16 | function getSuggestions() {
17 | ajax_call(false, 'suggest', escape(document.getElementById('query').value));
18 | }
19 |
20 | // Function handling the Ajax response:
21 | function handleSuggestions(res) {
22 | if (res.length > 0) {
23 | var e = document.getElementById('suggestions');
24 | e.innerHTML = '
close
';
25 | for (var i=0; i' + res[i] + '';
27 | }
28 | e.className = 'show';
29 | } else {
30 | clearSuggestions();
31 | }
32 | }
33 |
34 | function suggestOver(div_node) {
35 | div_node.className = div_node.className.replace('_normal', '_over');
36 | }
37 |
38 | function suggestOut(div_node) {
39 | div_node.className = div_node.className.replace('_over', '_normal');
40 | }
41 |
42 | function clearSuggestions() {
43 | var e = document.getElementById('suggestions');
44 | e.innerHTML = '';
45 | e.className = 'hide';
46 | }
47 |
48 | function setQuery(value) {
49 | document.getElementById('query').value = value;
50 | clearSuggestions();
51 | }
52 |
--------------------------------------------------------------------------------
/webware/Examples/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WebwareForPython/w4py3/7f75921926f58182cd3f28d5f974d13807df4ef6/webware/Examples/favicon.ico
--------------------------------------------------------------------------------
/webware/Examples/index.py:
--------------------------------------------------------------------------------
1 | from HTTPServlet import HTTPServlet
2 |
3 |
4 | class index(HTTPServlet):
5 |
6 | def respond(self, transaction):
7 | extraPath = transaction.request().extraURLPath()
8 | path = transaction.request().urlPath()
9 | if extraPath and path.endswith(extraPath):
10 | path = path[:-len(extraPath)]
11 | if not path.endswith('Welcome'):
12 | path = path.rpartition('/')[0] + '/Welcome' + extraPath
13 | # redirection via the server:
14 | transaction.application().forward(transaction, path)
15 | # redirection via the client:
16 | # trans.response().sendRedirect(path)
17 |
--------------------------------------------------------------------------------
/webware/MiscUtils/CSVJoiner.py:
--------------------------------------------------------------------------------
1 | """CSVJoiner.py
2 |
3 | A helper function for joining CSV fields.
4 | """
5 |
6 |
7 | def joinCSVFields(fields):
8 | """Create a CSV record by joining its fields.
9 |
10 | Returns a CSV record (e.g. a string) from a sequence of fields.
11 | Fields containing commands (,) or double quotes (") are quoted,
12 | and double quotes are escaped ("").
13 | The terminating newline is *not* included.
14 | """
15 | newFields = []
16 | for field in fields:
17 | if not isinstance(field, str):
18 | raise UnicodeDecodeError('CSV fields should be strings')
19 | if '"' in field:
20 | field = field.replace('"', '""')
21 | newField = f'"{field}"'
22 | elif ',' in field or '\n' in field or '\r' in field:
23 | newField = f'"{field}"'
24 | else:
25 | newField = field
26 | newFields.append(newField)
27 | return ','.join(newFields)
28 |
--------------------------------------------------------------------------------
/webware/MiscUtils/DateInterval.py:
--------------------------------------------------------------------------------
1 | """DateInterval.py
2 |
3 | Convert interval strings (in the form of 1w2d, etc) to seconds, and back again.
4 | Is not exactly about months or years (leap years in particular).
5 |
6 | Accepts (y)ear, (b)month, (w)eek, (d)ay, (h)our, (m)inute, (s)econd.
7 |
8 | Exports only timeEncode and timeDecode functions.
9 | """
10 |
11 | __all__ = ['timeEncode', 'timeDecode']
12 |
13 | import re
14 |
15 | from operator import itemgetter
16 |
17 | second = 1
18 | minute = second*60
19 | hour = minute*60
20 | day = hour*24
21 | week = day*7
22 | month = day*30
23 | year = day*365
24 | timeValues = {
25 | 'y': year,
26 | 'b': month,
27 | 'w': week,
28 | 'd': day,
29 | 'h': hour,
30 | 'm': minute,
31 | 's': second,
32 | }
33 | timeOrdered = sorted(timeValues.items(), key=itemgetter(1), reverse=True)
34 |
35 |
36 | def timeEncode(seconds):
37 | """Encode a number of seconds (representing a time interval).
38 |
39 | Encode the number into a form like 2d1h3s.
40 | """
41 | s = []
42 | for char, amount in timeOrdered:
43 | if seconds >= amount:
44 | i, seconds = divmod(seconds, amount)
45 | s.append(f'{i}{char}')
46 | return ''.join(s)
47 |
48 |
49 | _timeRE = re.compile(r'[0-9]+[a-zA-Z]')
50 |
51 |
52 | def timeDecode(s):
53 | """Decode a number in the format 1h4d3m (1 hour, 3 days, 3 minutes).
54 |
55 | Decode the format into a number of seconds.
56 | """
57 | time = 0
58 | for match in _timeRE.findall(s):
59 | char = match[-1].lower()
60 | try:
61 | time += int(match[:-1]) * timeValues[char]
62 | except KeyError:
63 | raise ValueError(f'Invalid unit of time: {char}') from None
64 | return time
65 |
--------------------------------------------------------------------------------
/webware/MiscUtils/DateParser.py:
--------------------------------------------------------------------------------
1 | """DateParser.py
2 |
3 | Convert string representations of dates to Python datetime objects.
4 |
5 | If installed, we will use the python-dateutil package to parse dates,
6 | otherwise we try to use the strptime function in the Python standard library
7 | with several frequently used formats.
8 | """
9 |
10 | __all__ = ['parseDateTime', 'parseDate', 'parseTime']
11 |
12 | try:
13 |
14 | from dateutil.parser import parse as parseDateTime
15 |
16 | except ImportError: # dateutil not available
17 |
18 | from datetime import datetime
19 |
20 | strpdatetime = datetime.strptime
21 |
22 | def parseDateTime(s):
23 | """Return a datetime object corresponding to the given string."""
24 | formats = (
25 | "%a %b %d %H:%M:%S %Y", "%a, %d-%b-%Y %H:%M:%S",
26 | "%Y-%m-%d %H:%M:%S", "%Y-%m-%dT%H:%M:%S",
27 | "%Y%m%d %H:%M:%S", "%Y%m%dT%H:%M:%S",
28 | "%Y%m%d %H%M%S", "%Y%m%dT%H%M%S",
29 | "%m/%d/%y %H:%M:%S", "%Y-%m-%d %H:%M",
30 | "%Y-%m-%d", "%Y%m%d", "%m/%d/%y",
31 | "%H:%M:%S", "%H:%M", "%c")
32 | for fmt in formats:
33 | try:
34 | return strpdatetime(s, fmt)
35 | except ValueError:
36 | pass
37 | raise ValueError(f'Cannot parse date/time {s}')
38 |
39 |
40 | def parseDate(s):
41 | """Return a date object corresponding to the given string."""
42 | return parseDateTime(s).date()
43 |
44 |
45 | def parseTime(s):
46 | """Return a time object corresponding to the given string."""
47 | return parseDateTime(s).time()
48 |
--------------------------------------------------------------------------------
/webware/MiscUtils/Error.py:
--------------------------------------------------------------------------------
1 | """Universal error class."""
2 |
3 |
4 | class Error(dict):
5 | """Universal error class.
6 |
7 | An error is a dictionary-like object, containing a specific
8 | user-readable error message and an object associated with it.
9 | Since Error inherits dict, other informative values can be arbitrarily
10 | attached to errors. For this reason, subclassing Error is rare.
11 |
12 | Example::
13 |
14 | err = Error(user, 'Invalid password.')
15 | err['time'] = time.time()
16 | err['attempts'] = attempts
17 |
18 | The object and message can be accessed via methods::
19 |
20 | print(err.object())
21 | print(err.message())
22 |
23 | When creating errors, you can pass None for both object and message.
24 | You can also pass additional values, which are then included in the error::
25 |
26 | >>> err = Error(None, 'Too bad.', timestamp=time.time())
27 | >>> err.keys()
28 | ['timestamp']
29 |
30 | Or include the values as a dictionary, instead of keyword arguments::
31 |
32 | >>> info = {'timestamp': time.time()}
33 | >>> err = Error(None, 'Too bad.', info)
34 |
35 | Or you could even do both if you needed to.
36 | """
37 |
38 | def __init__(self, obj, message, valueDict=None, **valueArgs):
39 | """Initialize the error.
40 |
41 | Takes the object the error occurred for, and the user-readable
42 | error message. The message should be self sufficient such that
43 | if printed by itself, the user would understand it.
44 | """
45 | dict.__init__(self)
46 | self._object = obj
47 | self._message = message
48 | if valueDict:
49 | self.update(valueDict)
50 | self.update(valueArgs)
51 |
52 | def object(self):
53 | """Get the object the error occurred for."""
54 | return self._object
55 |
56 | def message(self):
57 | """Get the user-readable error message."""
58 | return self._message
59 |
60 | def __repr__(self):
61 | return (f'ERROR(object={self._object!r};'
62 | f' message={self._message!r}; data={dict(self)!r})')
63 |
64 | def __str__(self):
65 | return f'ERROR: {self._message}'
66 |
67 | def __bool__(self):
68 | return True
69 |
--------------------------------------------------------------------------------
/webware/MiscUtils/M2PickleRPC.py:
--------------------------------------------------------------------------------
1 | """M2Crypto-enhanced transport for PickleRPC
2 |
3 | This lets you use M2Crypto for SSL encryption.
4 |
5 | Based on m2xmlrpclib.py which is
6 | Copyright (c) 1999-2002 Ng Pheng Siong. All rights reserved.
7 | """
8 |
9 | from io import BytesIO
10 |
11 | from M2Crypto import SSL, httpslib, m2urllib # pylint: disable=import-error
12 |
13 | from .PickleRPC import Transport
14 |
15 | __version__ = 1 # version of M2PickleRPC
16 |
17 |
18 | class M2Transport(Transport):
19 |
20 | user_agent = f"M2PickleRPC.py/{__version__} - {Transport.user_agent}"
21 |
22 | def __init__(self, ssl_context=None):
23 | self.ssl_context = ssl_context or SSL.Context('sslv23')
24 |
25 | def make_connection(self, host, port=None):
26 | if port is None:
27 | host, port = m2urllib.splitport(host)
28 | return httpslib.HTTPS(host, int(port), ssl_context=self.ssl_context)
29 |
30 | # Workarounds below are necessary because M2Crypto seems to
31 | # return from fileobject.read() early! So we have to call it
32 | # over and over to get the full data.
33 |
34 | def parse_response(self, f):
35 | """Workaround M2Crypto issue mentioned above."""
36 | sio = BytesIO()
37 | while True:
38 | chunk = f.read()
39 | if not chunk:
40 | break
41 | sio.write(chunk)
42 | sio.seek(0)
43 | return Transport.parse_response(self, sio)
44 |
45 | def parse_response_gzip(self, f):
46 | """Workaround M2Crypto issue mentioned above."""
47 | sio = BytesIO()
48 | while True:
49 | chunk = f.read()
50 | if not chunk:
51 | break
52 | sio.write(chunk)
53 | sio.seek(0)
54 | return Transport.parse_response_gzip(self, sio)
55 |
--------------------------------------------------------------------------------
/webware/MiscUtils/ParamFactory.py:
--------------------------------------------------------------------------------
1 | """ParamFactory.py
2 |
3 | A factory for creating cached, parametrized class instances.
4 | """
5 |
6 | from threading import Lock
7 |
8 |
9 | class ParamFactory:
10 |
11 | def __init__(self, klass, **extraMethods):
12 | self.lock = Lock()
13 | self.cache = {}
14 | self.klass = klass
15 | for name, func in list(extraMethods.items()):
16 | setattr(self, name, func)
17 |
18 | def __call__(self, *args):
19 | with self.lock:
20 | if args in self.cache:
21 | value = self.cache[args]
22 | else:
23 | value = self.klass(*args)
24 | self.cache[args] = value
25 | return value
26 |
27 | def allInstances(self):
28 | return list(self.cache.values())
29 |
--------------------------------------------------------------------------------
/webware/MiscUtils/Properties.py:
--------------------------------------------------------------------------------
1 | name = 'MiscUtils'
2 |
3 | status = 'stable'
4 |
5 | synopsis = (
6 | "MiscUtils provides support classes and functions to Webware"
7 | " that aren't necessarily web-related and that don't fit into one of"
8 | " the other plug-ins. There is plenty of useful reusable code here.")
9 |
--------------------------------------------------------------------------------
/webware/MiscUtils/Tests/BenchCSVParser.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import time
3 | from glob import glob
4 | try:
5 | from cProfile import Profile
6 | except ImportError:
7 | from profile import Profile
8 |
9 | from MiscUtils.CSVParser import CSVParser
10 |
11 |
12 | class BenchCSVParser:
13 |
14 | def __init__(self, profile=False, runTestSuite=True):
15 | self.parse = CSVParser().parse
16 | self._shouldProfile = profile
17 | self._shouldRunTestSuite = runTestSuite
18 | self._iterations = 1000
19 |
20 | def main(self):
21 | print('Benchmarking CSVParser ...')
22 | print()
23 | if len(sys.argv) > 1 and sys.argv[1].lower().startswith('prof'):
24 | self._shouldProfile = True
25 | if self._shouldRunTestSuite:
26 | print('Running test suite ...')
27 | from unittest import main
28 | main('MiscUtils.Tests.TestCSVParser', exit=False)
29 | start = time.time()
30 | if self._shouldProfile:
31 | prof = Profile()
32 | prof.runcall(self._main)
33 | filename = f'{self.__class__.__name__}.pstats'
34 | prof.dump_stats(filename)
35 | print('Wrote', filename)
36 | else:
37 | self._main()
38 | duration = time.time() - start
39 | print(f'{duration:.1f} secs')
40 |
41 | def _main(self):
42 | print()
43 | for name in glob('Sample*.csv'):
44 | print("Benchmark using", name, "...")
45 | self.benchFileNamed(name)
46 |
47 | def benchFileNamed(self, name, encoding='utf-8'):
48 | with open(name, encoding=encoding) as f:
49 | lines = f.readlines()
50 | for line in lines:
51 | for _iteration in range(self._iterations):
52 | # we duplicate lines to reduce the overhead of the loop
53 | self.parse(line)
54 | self.parse(line)
55 | self.parse(line)
56 | self.parse(line)
57 | self.parse(line)
58 | self.parse(line)
59 | self.parse(line)
60 | self.parse(line)
61 | self.parse(line)
62 | self.parse(line)
63 | self.parse(line)
64 | self.parse(line)
65 | self.parse(line)
66 | self.parse(line)
67 | self.parse(line)
68 | self.parse(line)
69 |
70 |
71 | if __name__ == '__main__':
72 | BenchCSVParser().main()
73 |
--------------------------------------------------------------------------------
/webware/MiscUtils/Tests/BenchDataTable.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import sys
4 | import time
5 | from glob import glob
6 | try:
7 | from cProfile import Profile
8 | except ImportError:
9 | from profile import Profile
10 |
11 | from MiscUtils.DataTable import DataTable
12 |
13 |
14 | class BenchDataTable:
15 |
16 | def __init__(self, profile=False, runTestSuite=True):
17 | self._shouldProfile = profile
18 | self._shouldRunTestSuite = runTestSuite
19 | self._iterations = 1000
20 |
21 | def main(self):
22 | print('Benchmarking DataTable ...')
23 | print()
24 | if len(sys.argv) > 1 and sys.argv[1].lower().startswith('prof'):
25 | self._shouldProfile = True
26 | if self._shouldRunTestSuite:
27 | print('Running test suite ...')
28 | from unittest import main
29 | main('MiscUtils.Tests.TestDataTable', exit=False)
30 | start = time.time()
31 | if self._shouldProfile:
32 | prof = Profile()
33 | prof.runcall(self._main)
34 | filename = f'{self.__class__.__name__}.pstats'
35 | prof.dump_stats(filename)
36 | print('Wrote', filename)
37 | else:
38 | self._main()
39 | duration = time.time() - start
40 | print(f'{duration:.1f} secs')
41 |
42 | def _main(self):
43 | print()
44 | for name in glob('Sample*.csv'):
45 | print("Benchmark using", name, "...")
46 | self.benchFileNamed(name)
47 |
48 | def benchFileNamed(self, name, encoding='utf-8'):
49 | with open(name, encoding=encoding) as f:
50 | contents = f.read()
51 | for _iteration in range(self._iterations):
52 | # we duplicate lines to reduce the overhead of the loop
53 | dt = DataTable()
54 | dt.readString(contents)
55 | dt = DataTable()
56 | dt.readString(contents)
57 | dt = DataTable()
58 | dt.readString(contents)
59 | dt = DataTable()
60 | dt.readString(contents)
61 | dt = DataTable()
62 | dt.readString(contents)
63 | dt = DataTable()
64 | dt.readString(contents)
65 | dt = DataTable()
66 | dt.readString(contents)
67 | dt = DataTable()
68 | dt.readString(contents)
69 |
70 |
71 | if __name__ == '__main__':
72 | BenchDataTable().main()
73 |
--------------------------------------------------------------------------------
/webware/MiscUtils/Tests/Sample.csv:
--------------------------------------------------------------------------------
1 | Class,Attribute,Type,isRequired,Min,Max,Extras
2 | Video,,,,,,isAbstract=1
3 | ,title,string,1,1,100,
4 | ,directors,list of Person,0,,10,
5 | ,cast,list of Role,0,,,
6 | Movie (Video),,,,,,
7 | ,year,int,1,,,
8 | ,rating,enum,1,,,"Enums='g, pg, pg13, r, nc17, x, nr, other'"
9 | TVSeries (Video),,,,,,
10 | ,years,int,,,,Comment='supposed to be pickle; a list of ints'
11 | Person,,,,,,
12 | ,video,Video,0,,,Comment='back pointer to View for directors attr'
13 | ,name,string,1,1,100,
14 | ,birthDate,date,0,,50,
15 | Role,,,,,,
16 | ,video,Video,1,,,
17 | ,karacter,string,1,,100
18 | ,person,Person,1,,
19 |
--------------------------------------------------------------------------------
/webware/MiscUtils/Tests/Sample.xls:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WebwareForPython/w4py3/7f75921926f58182cd3f28d5f974d13807df4ef6/webware/MiscUtils/Tests/Sample.xls
--------------------------------------------------------------------------------
/webware/MiscUtils/Tests/TestDBPool.py:
--------------------------------------------------------------------------------
1 | import sqlite3
2 | import unittest
3 |
4 | from MiscUtils.DBPool import DBPool
5 |
6 |
7 | class TestDBPool(unittest.TestCase):
8 |
9 | def testDbPool(self):
10 | pool = DBPool(sqlite3, 10, database=':memory:')
11 | for _count in range(15):
12 | con = pool.connection()
13 | cursor = con.cursor()
14 | cursor.execute("select 1 union select 2 union select 3 order by 1")
15 | rows = cursor.fetchall()
16 | self.assertEqual(rows, [(1,), (2,), (3,)])
17 | con.close()
18 |
--------------------------------------------------------------------------------
/webware/MiscUtils/Tests/TestDateInterval.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from MiscUtils.DateInterval import timeEncode, timeDecode
4 |
5 |
6 | class TestDateInterval(unittest.TestCase):
7 |
8 | def testTimeEncode(self):
9 | self.assertEqual(timeEncode(1), '1s')
10 | self.assertEqual(timeEncode(60), '1m')
11 | self.assertEqual(timeEncode(176403), '2d1h3s')
12 | self.assertEqual(timeEncode(349380), '4d1h3m')
13 | self.assertEqual(timeEncode(38898367), '1y2b3w4d5h6m7s')
14 |
15 | def testTimeDecode(self):
16 | self.assertEqual(timeDecode('1s'), 1)
17 | self.assertEqual(timeDecode('1h2d3s'), 176403)
18 | self.assertEqual(timeDecode('2d1h3s'), 176403)
19 | self.assertEqual(timeDecode('1h4d3m'), 349380)
20 | self.assertEqual(timeDecode('3m4d1h'), 349380)
21 | self.assertEqual(timeDecode('1y2b3w4d5h6m7s'), 38898367)
22 | self.assertEqual(timeDecode('0y1b2w3d4h5m6s'), 4075506)
23 | self.assertEqual(timeDecode('6s5m4h3d2w1b0y'), 4075506)
24 | self.assertEqual(timeDecode('(3s-2d-1h)'), 176403)
25 | try:
26 | timeDecode('1h5n')
27 | except ValueError as e:
28 | self.assertEqual(str(e), 'Invalid unit of time: n')
29 |
--------------------------------------------------------------------------------
/webware/MiscUtils/Tests/TestError.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from MiscUtils.Error import Error
4 |
5 |
6 | class DummyObject:
7 |
8 | def __repr__(self):
9 | return ''
10 |
11 |
12 | class TestError(unittest.TestCase):
13 |
14 | def testNone(self):
15 | err = Error(None, None)
16 | self.assertEqual(str(err), 'ERROR: None')
17 | self.assertEqual(repr(err),
18 | 'ERROR(object=None; message=None; data={})')
19 | self.assertIs(err.object(), None)
20 | self.assertIs(err.message(), None)
21 |
22 | def testObjMessage(self):
23 | obj = DummyObject()
24 | err = Error(obj, 'test')
25 | self.assertEqual(str(err), 'ERROR: test')
26 | self.assertEqual(
27 | repr(err),
28 | "ERROR(object=; message='test'; data={})")
29 | self.assertIs(err.object(), obj)
30 | self.assertIs(err.message(), 'test')
31 |
32 | def testIsDict(self):
33 | err = Error(DummyObject(), 'test')
34 | self.assertIsInstance(err, dict)
35 | self.assertIsInstance(err, Error)
36 |
37 | def testValueDict(self):
38 | err = Error(DummyObject(), 'test', a=5, b='.')
39 | self.assertEqual(str(err), 'ERROR: test')
40 | self.assertEqual(
41 | repr(err),
42 | "ERROR(object=; message='test'; data={'a': 5, 'b': '.'})")
43 | self.assertEqual(list(err), ['a', 'b'])
44 | self.assertIsInstance(err['a'], int)
45 | self.assertIsInstance(err['b'], str)
46 |
47 | def testVarArgs(self):
48 | err = Error(DummyObject(), 'test', {'a': 5}, b='.')
49 | self.assertEqual(str(err), 'ERROR: test')
50 | self.assertEqual(
51 | repr(err),
52 | "ERROR(object=; message='test'; data={'a': 5, 'b': '.'})")
53 | self.assertEqual(list(err), ['a', 'b'])
54 | self.assertIsInstance(err['a'], int)
55 | self.assertIsInstance(err['b'], str)
56 |
--------------------------------------------------------------------------------
/webware/MiscUtils/Tests/TestPickleCache.py:
--------------------------------------------------------------------------------
1 | import os
2 | import time
3 | import shutil
4 | import tempfile
5 | import unittest
6 |
7 | from MiscUtils import PickleCache as pc
8 |
9 |
10 | class TestPickleCache(unittest.TestCase):
11 |
12 | def setUp(self):
13 | self._tempDir = tempfile.mkdtemp()
14 |
15 | def tearDown(self):
16 | shutil.rmtree(self._tempDir, ignore_errors=True)
17 |
18 | @staticmethod
19 | def remove(filename):
20 | try:
21 | os.remove(filename)
22 | except OSError:
23 | pass
24 |
25 | def testTwoIterations(self):
26 | iterations = 2
27 | for _iteration in range(iterations):
28 | self.testOneIteration()
29 |
30 | def testOneIteration(self):
31 | sourcePath = self._sourcePath = os.path.join(self._tempDir, 'foo.dict')
32 | picklePath = self._picklePath = pc.PickleCache().picklePath(sourcePath)
33 | self.remove(picklePath) # make sure we're clean
34 | data = self._data = {'x': 1}
35 | self.writeSource()
36 | try:
37 | # test 1: no pickle cache yet
38 | self.assertTrue(pc.readPickleCache(sourcePath) is None)
39 | self.writePickle()
40 | # test 2: correctness
41 | self.assertEqual(pc.readPickleCache(sourcePath), data)
42 | # test 3: wrong pickle version
43 | self.assertTrue(
44 | pc.readPickleCache(sourcePath, pickleProtocol=1) is None)
45 | self.writePickle() # restore
46 | # test 4: wrong data source
47 | self.assertTrue(
48 | pc.readPickleCache(sourcePath, source='notTest') is None)
49 | self.writePickle() # restore
50 | # test 5: wrong Python version
51 | v = list(pc.versionInfo)
52 | v[-1] += 1 # increment serial number
53 | v, pc.versionInfo = pc.versionInfo, tuple(v)
54 | try:
55 | self.assertTrue(pc.readPickleCache(sourcePath) is None)
56 | self.writePickle() # restore
57 | finally:
58 | pc.versionInfo = v
59 | # test 6: source is newer
60 | self.remove(picklePath)
61 | self.writePickle()
62 | # we have to allow for the granularity of getmtime()
63 | # (see the comment in the docstring of PickleCache.py)
64 | time.sleep(1.25)
65 | self.writeSource()
66 | self.assertTrue(pc.readPickleCache(sourcePath) is None)
67 | self.writePickle() # restore
68 | finally:
69 | self.remove(sourcePath)
70 | self.remove(picklePath)
71 |
72 | def writeSource(self):
73 | with open(self._sourcePath, 'w', encoding='ascii') as f:
74 | f.write(str(self._data))
75 |
76 | def writePickle(self):
77 | self.assertFalse(os.path.exists(self._picklePath))
78 | pc.writePickleCache(self._data, self._sourcePath, source='test')
79 | self.assertTrue(os.path.exists(self._picklePath))
80 |
--------------------------------------------------------------------------------
/webware/MiscUtils/Tests/__init__.py:
--------------------------------------------------------------------------------
1 | """MiscUtils Tests"""
2 |
--------------------------------------------------------------------------------
/webware/MiscUtils/__init__.py:
--------------------------------------------------------------------------------
1 | """MiscUtils for Webware for Python"""
2 |
3 | __all__ = ['AbstractError', 'NoDefault']
4 |
5 |
6 | class AbstractError(NotImplementedError):
7 | """Abstract method error.
8 |
9 | This exception is raised by abstract methods in abstract classes.
10 | It is a special case of NotImplementedError, that indicates that the
11 | implementation won't ever be provided at that location in the future
12 | -- instead the subclass should provide it.
13 |
14 | Typical usage:
15 |
16 | from MiscUtils import AbstractError
17 |
18 | class Foo:
19 | def bar(self):
20 | raise AbstractError(self.__class__)
21 |
22 | Note that adding the self.__class__ makes the resulting exception
23 | *much* more useful.
24 | """
25 |
26 |
27 | class NoDefault:
28 | """Singleton for parameters with no default.
29 |
30 | This provides a singleton "thing" which can be used to initialize
31 | the "default=" arguments for different retrieval methods.
32 |
33 | For example:
34 |
35 | from MiscUtils import NoDefault
36 | def bar(self, name, default=NoDefault):
37 | if default is NoDefault:
38 | return self._bars[name] # will raise exception for invalid key
39 | else:
40 | return self._bars.get(name, default)
41 |
42 | The value None does not suffice for "default=" because it does not
43 | indicate whether or not a value was passed.
44 |
45 | Consistently using this singleton is valuable due to subclassing
46 | situations:
47 |
48 | def bar(self, name, default=NoDefault):
49 | if someCondition:
50 | return self.specialBar(name)
51 | else:
52 | return SuperClass.bar(name, default)
53 |
54 | It's also useful if one method that uses "default=NoDefault" relies
55 | on another object and method to which it must pass the default.
56 | (This is similar to the subclassing situation.)
57 | """
58 |
59 |
60 | def installInWebware(_application):
61 | pass
62 |
--------------------------------------------------------------------------------
/webware/MockApplication.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | from ConfigurableForServerSidePath import ConfigurableForServerSidePath
4 |
5 |
6 | class MockImportManager:
7 |
8 | def recordFile(self, filename, isfile=None):
9 | pass
10 |
11 |
12 | defaultConfig = {
13 | 'CacheDir': 'Cache',
14 | 'PlugIns': ['MiscUtils', 'WebUtils', 'TaskKit', 'UserKit', 'PSP'],
15 | 'PrintPlugIns': False
16 | }
17 |
18 |
19 | class MockApplication(ConfigurableForServerSidePath):
20 | """
21 | A minimal implementation which is compatible with Application
22 | and which is sufficient to load plugins.
23 | """
24 |
25 | def __init__(self, path=None, settings=None, development=None):
26 | ConfigurableForServerSidePath.__init__(self)
27 | if path is None:
28 | path = os.getcwd()
29 | self._serverSidePath = os.path.abspath(path)
30 | self._webwarePath = os.path.abspath(os.path.dirname(__file__))
31 | if development is None:
32 | development = bool(os.environ.get('WEBWARE_DEVELOPMENT'))
33 | self._development = development
34 | appConfig = self.config()
35 | if settings:
36 | appConfig.update(settings)
37 | self._cacheDir = self.serverSidePath(
38 | self.setting('CacheDir') or 'Cache')
39 | from MiscUtils.PropertiesObject import PropertiesObject
40 | props = PropertiesObject(os.path.join(
41 | self._webwarePath, 'Properties.py'))
42 | self._webwareVersion = props['version']
43 | self._webwareVersionString = props['versionString']
44 | self._imp = MockImportManager()
45 | for path in (self._cacheDir,):
46 | if path and not os.path.exists(path):
47 | os.makedirs(path)
48 |
49 | def defaultConfig(self):
50 | return defaultConfig
51 |
52 | def configReplacementValues(self):
53 | """Get config values that need to be escaped."""
54 | return {
55 | 'ServerSidePath': self._serverSidePath,
56 | 'WebwarePath': self._webwarePath,
57 | 'Development': self._development
58 | }
59 |
60 | def configFilename(self):
61 | return self.serverSidePath('Configs/Application.config')
62 |
63 | def serverSidePath(self, path=None):
64 | if path:
65 | return os.path.normpath(
66 | os.path.join(self._serverSidePath, path))
67 | return self._serverSidePath
68 |
69 | def hasContext(self, _name):
70 | return False
71 |
72 | def addServletFactory(self, factory):
73 | pass
74 |
--------------------------------------------------------------------------------
/webware/PSP/CompilePSP.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import os
4 | import sys
5 |
6 |
7 | def compilePSP(*args):
8 | from .Context import PSPCLContext
9 | from .PSPCompiler import Compiler
10 | pspFilename = args[0]
11 | fil, ext = os.path.splitext(os.path.basename(pspFilename))
12 | classname = fil + '_' + ext
13 | pyFilename = classname + '.py'
14 | context = PSPCLContext(pspFilename)
15 | context.setClassName(classname)
16 | context.setPythonFileName(pyFilename)
17 | context.setPythonFileEncoding('utf-8')
18 | clc = Compiler(context)
19 | clc.compile()
20 |
21 |
22 | if __name__ == '__main__':
23 | path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
24 | sys.path.insert(0, path)
25 | from PSP.CompilePSP import compilePSP as main
26 | main(sys.argv[1])
27 |
--------------------------------------------------------------------------------
/webware/PSP/Examples/Braces.psp:
--------------------------------------------------------------------------------
1 | <%@ page imports = "sys,os,time,PSP.Examples.PSPExamplePage:PSPExamplePage"%><%-- Here's the modules that I need in this file. --%>
2 | <%@ page method="writeContent" %> <%-- This is the method of the base class that I want to override, writeHTML is the default --%>
3 | <%@ page extends="PSPExamplePage"%> <%--This is the base class for this page. Page is the default --%>
4 | <%@ page indentType="braces" %>
5 | <%--
6 | <%@ page method="writeBody" %>
7 | --%>
8 |
9 |
10 | return "Braces Test"
11 |
12 |
13 |
Python Server Pages
14 |
15 |
Braces Test
16 |
17 |
Dave Wallace (dwallace@delanet.com) has written a module that will convert Python code that uses braces for indentation into properly whitespace indented python syntax. This is pretty nifty, and very useful for PSP.
18 |
19 |
The whitespace significance in Python syntax is difficult in PSP because HTML is the exact opposite. Whitespace doesn't matter, and neither do carriage-returns or anything else. So that makes the melding of Python and HTML a challenge.
20 |
21 |
So this is one solution.
22 |
23 |
Take out all of the whitespace significance, and just use braces.
24 |
25 |
This module allows you to use braces where you would normally be hitting <return><tab>. You can put the opening brace ({) on the line befoe the block starts, or on the first line of the block. Same kind of deal for the closing brace (}). You still need to have the colon (:) on the line right before the block starts. That's just part of Python.
26 |
27 |
To enable this functionality in PSP, you have to set braces as the indent type for your page. So you add this directive to your PSP page:
28 |
29 |
<%@ page indentType="braces" %>
30 |
31 |
This is a little test of the functionality:
32 |
33 |
<% for i in range(5): { %>
34 |
I'm number <%=i+1%>
35 | <% } %>
36 |
37 |
Click on "View source" over on the left to see the source for this page.
38 |
39 |
See also the PSP documentation on braces.
40 |
--------------------------------------------------------------------------------
/webware/PSP/Examples/Hello.psp:
--------------------------------------------------------------------------------
1 | <%-- This is a PSP comment. It won't show up in the HTML or even in the class that this file will generate --%>
2 |
3 | <%@ page imports = "sys,os,time,PSP.Examples.PSPExamplePage:PSPExamplePage"%><%-- Here's the modules that I need in this file. --%>
4 | <%@ page method="writeContent" %><%-- This is the method of the base class that I want to override, writeHTML is the default. --%>
5 | <%@ page extends="PSPExamplePage"%><%--This is the base class for this page. Page is the default. --%>
6 | <%@ page isInstanceSafe="yes" %><%-- Each instance of this class can be used multiple times. --%>
7 | <%@ page indentType="spaces" %><%-- Use spaces to indent the sourcefile that this template will produce. --%>
8 |
9 | <%-- Method declaration Test --%>
10 |
11 | return "PSP Hello"
12 |
13 |
14 |
Hello from PSP!
15 |
16 | <%-- This image is served by Webware --%>
17 |
18 |
19 |
This is PSP. Please read the user's guide for more information.
20 |
21 |
Here are some examples. PSPTests shows most of the functionality:
22 |
23 |
<%
24 | import glob
25 | filePath = self.request().serverSidePath()
26 | files = glob.glob(os.path.join(os.path.dirname(filePath), "*.psp"))
27 | for path in files:
28 | name = os.path.split(path)[1]$%><%-- Aha! Here's where we need the complex block syntax. Ok. --%>
29 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/webware/PSP/Examples/View.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | from .PSPExamplePage import PSPExamplePage
4 |
5 |
6 | class View(PSPExamplePage):
7 | """View the source of a PSP page.
8 |
9 | For each PSP example, you will see a sidebar with various menu items,
10 | one of which is "View source of example". This link points to the
11 | View servlet and passes the filename of the current servlet. The View
12 | servlet then loads that PSP file's source code and displays it in the
13 | browser for your viewing pleasure.
14 |
15 | Note that if the View servlet isn't passed a PSP filename, it prints the
16 | View's docstring which you are reading right now.
17 | """
18 |
19 | def writeContent(self):
20 | req = self.request()
21 | if req.hasField('filename'):
22 | filename = req.field('filename')
23 | basename = os.path.basename(filename)
24 | filename = self.request().serverSidePath(basename)
25 | if not os.path.exists(filename):
26 | self.write('
'
27 | f'No such file {basename} exists
')
28 | return
29 | with open(filename, encoding='utf-8') as pspFile:
30 | text = pspFile.read()
31 | text = self.htmlEncode(text)
32 | text = text.replace('\n', ' ').replace('\t', ' '*4)
33 | self.write(f'
8 |
9 |
10 |
--------------------------------------------------------------------------------
/webware/Testing/EmbeddedServlet.py:
--------------------------------------------------------------------------------
1 | from Page import Page
2 |
3 |
4 | class EmbeddedServlet(Page):
5 | """Test extra path info.
6 |
7 | This servlet serves as a test for "extra path info"-style URLs such as:
8 |
9 | http://localhost/Webware/Servlet/Extra/Path/Info
10 |
11 | Where the servlet is embedded in the URL, rather than being the last
12 | component. This servlet simply prints its fields.
13 | """
14 |
15 | def writeBody(self):
16 | fields = self.request().fields()
17 | self.writeln('
EmbeddedServlet
')
18 | self.writeln(f'
{self.__class__.__doc__}
')
19 | self.writeln(f'
Fields: {len(fields)}
')
20 | self.writeln('
')
21 | enc = self.htmlEncode
22 | for key, value in fields.items():
23 | self.writeln(f'