Révision b77557ca
Ajouté par dlaniel il y a presque 18 ans
- ID b77557ca44ce9cfe58c805ea6c82667fb60a0af5
TODO | ||
---|---|---|
1 |
- interface d'admin, permettant de d?finir les points ci-dessus, (?criture d'un fichier inclus dans la conf apache puis reload) |
|
2 |
- options possibles par serveur vhost/r?pertoire |
|
3 |
- [x] acc?s n?cessitant authentification (?a, ?a ferait un ?quivalent LemonLDAP) |
|
4 |
- possibilit? interception URL /login |
|
5 |
- pour en faire libertysation: |
|
6 |
- redirect, ... |
|
7 |
- page proposant d'?tablir la f?d?ration, o? l'utilisateur entre identifiant / mot de passe du SP |
|
8 |
|
|
9 |
|
|
10 |
Fait |
|
11 |
==== |
|
12 |
|
|
13 |
- rp par vhost (appli1.example.com, rp de appli1.interne) |
|
14 |
- rp par repertoire (www.example.com/appli1, rp de appl1.interne) |
admin/__init__.py | ||
---|---|---|
1 |
from quixote.server.scgi_server import run |
|
2 |
from quixote.publish import Publisher |
|
3 |
from quixote import enable_ptl |
|
4 |
import quixote |
|
5 |
enable_ptl() |
|
6 |
|
|
7 |
from root import RootDirectory |
|
8 |
|
|
9 |
def create_publisher(): |
|
10 |
return Publisher(RootDirectory()) |
|
11 |
|
|
12 |
#quixote.DEFAULT_CHARSET = 'utf-8' |
|
13 |
run(create_publisher, port=3003, script_name = '') |
admin/apache.ptl | ||
---|---|---|
1 |
from quixote.directory import Directory |
|
2 |
|
|
3 |
from template import generate_html |
|
4 |
from apache_edit import ApacheEdit |
|
5 |
from apache_form import ApacheForm |
|
6 |
from apache_vhost import ApacheVhost |
|
7 |
|
|
8 |
class Apache(Directory): |
|
9 |
""" |
|
10 |
Apache administration directory |
|
11 |
""" |
|
12 |
_q_exports = ['', 'new', 'edit'] |
|
13 |
|
|
14 |
edit = ApacheEdit() |
|
15 |
|
|
16 |
def _q_index [html] (self): |
|
17 |
#form = ApacheForm() |
|
18 |
body = '<a href="new">Cr?er un nouvel h?te virtuel</a>' |
|
19 |
generate_html('apache', body) |
|
20 |
#generate_html('apache', self.generate_body()) |
|
21 |
|
|
22 |
# def _q_lookup [html] (self, component): |
|
23 |
# vhost = ApacheVhost(component) |
|
24 |
# vhost._q_lookup(component) |
|
25 |
# return component |
|
26 |
|
|
27 |
def new [html] (self): |
|
28 |
form = ApacheForm() |
|
29 |
generate_html('apache', form.handle()) |
admin/apache_edit.ptl | ||
---|---|---|
1 |
from quixote.directory import Directory |
|
2 |
|
|
3 |
from template import generate_html |
|
4 |
from apache_form import ApacheForm |
|
5 |
|
|
6 |
class ApacheEdit(Directory): |
|
7 |
""" |
|
8 |
Apache administration directory |
|
9 |
""" |
|
10 |
_q_exports = [''] |
|
11 |
|
|
12 |
def _q_index [html] (self): |
|
13 |
"""Error""" |
|
14 |
return |
|
15 |
|
|
16 |
def _q_lookup [html] (self, server_name): |
|
17 |
form = ApacheForm(server_name) |
|
18 |
generate_html('apache', form.handle()) |
admin/apache_form.ptl | ||
---|---|---|
1 |
import os |
|
2 |
|
|
3 |
from quixote.publish import get_response |
|
4 |
from quixote.form import Form, StringWidget, PasswordWidget, \ |
|
5 |
RadiobuttonsWidget, SingleSelectWidget, MultipleSelectWidget, \ |
|
6 |
CheckboxWidget, FileWidget |
|
7 |
|
|
8 |
class ApacheForm(Form): |
|
9 |
def __init__(self, server_name = None): |
|
10 |
Form.__init__(self) |
|
11 |
#get_response().set_charset('utf-8') |
|
12 |
self.server_name = server_name |
|
13 |
|
|
14 |
def handle(self): |
|
15 |
if (self.server_name is None): |
|
16 |
self.add_string(name = 'proxy_name', title = 'Nom du serveur proxy', required = True) |
|
17 |
else: |
|
18 |
self.add_string(name = 'proxy_name', title = 'Nom du serveur proxy', value = self.server_name, required = True) |
|
19 |
self.add_string(name = 'proxy_ip', title = 'Adresse IP du serveur proxy', required = True) |
|
20 |
self.add_string(name = 'proxy_port', title = 'Port du serveur proxy', value = '80', required = True) |
|
21 |
#self.add_string(name = 'proxy_admin', title = 'Administrateur du serveur proxy', required = False) |
|
22 |
self.add_string(name = 'site_address', title = 'Adresse du site', value = 'http://', required = True) |
|
23 |
self.add_string(name = 'proxy_directory', title = 'R?pertoire du proxy', value = '/', required = True) |
|
24 |
self.add_submit('submit') |
|
25 |
if not self.is_submitted() or self.has_errors(): |
|
26 |
return self.render() |
|
27 |
return self.success() |
|
28 |
|
|
29 |
def success [html] (self): |
|
30 |
values = {} |
|
31 |
#get_response().set_charset('utf-8') |
|
32 |
for widget in self.get_all_widgets(): |
|
33 |
values[widget.get_name()] = widget.parse() |
|
34 |
|
|
35 |
#"""<p>name : """ |
|
36 |
#name |
|
37 |
#"""</p><p>value : """ |
|
38 |
#repr(value) |
|
39 |
#"""</p>""" |
|
40 |
|
|
41 |
try: |
|
42 |
self.write_apache_conf(values) |
|
43 |
#self.write_apache_conf(values['proxy_name'], |
|
44 |
# values['proxy_ip'], |
|
45 |
# values['proxy_port'], |
|
46 |
# values['site_address'], |
|
47 |
# values['proxy_directory']) |
|
48 |
except IOError: |
|
49 |
"""Failed writing apache configuration""" |
|
50 |
else: |
|
51 |
"""Apache configuration written""" |
|
52 |
|
|
53 |
#def write_apache_conf(self, proxy_name, proxy_ip, proxy_port, site_address, proxy_directory): |
|
54 |
def write_apache_conf(self, values): |
|
55 |
"""Caller must catch IOError exceptions""" |
|
56 |
#print ip + " - " + name + " - " + directory |
|
57 |
vhost_dir = '/var/lib/larpe' |
|
58 |
conf_dir = '%s/%s' % (vhost_dir, values['proxy_name']) |
|
59 |
if not os.path.exists(conf_dir): |
|
60 |
os.makedirs(conf_dir, mode = 0755) |
|
61 |
if not os.path.isdir(conf_dir): |
|
62 |
raise IOError() |
|
63 |
conf_file = open('%s/%s' % (conf_dir, 'apache2-vhost.conf'), 'w') |
|
64 |
|
|
65 |
try: |
|
66 |
conf_file.write("""NameVirtualHost %(proxy_ip)s:%(proxy_port)s |
|
67 |
<VirtualHost %(proxy_ip)s:%(proxy_port)s> |
|
68 |
|
|
69 |
ServerName %(proxy_name)s |
|
70 |
# ServerAdmin admin@test.org |
|
71 |
|
|
72 |
# LoadFile /usr/lib/libxml2.so.2 |
|
73 |
SetOutputFilter proxy-html |
|
74 |
|
|
75 |
<Location %(proxy_directory)s> |
|
76 |
ProxyPass %(site_address)s |
|
77 |
ProxyPassReverse %(site_address)s |
|
78 |
ProxyHTMLURLMap / %(proxy_directory)s |
|
79 |
ProxyHTMLURLMap %(site_address)s %(proxy_directory)s |
|
80 |
</Location> |
|
81 |
|
|
82 |
</VirtualHost> |
|
83 |
""" % values) |
|
84 |
|
|
85 |
finally: |
|
86 |
conf_file.close() |
|
87 |
|
|
88 |
|
admin/apache_new.ptl | ||
---|---|---|
1 |
from quixote.directory import Directory |
|
2 |
|
|
3 |
from template import generate_html |
|
4 |
from apache_form import ApacheForm |
|
5 |
|
|
6 |
class ApacheNew(Directory): |
|
7 |
""" |
|
8 |
Apache administration directory |
|
9 |
""" |
|
10 |
_q_exports = [''] |
|
11 |
|
|
12 |
def _q_index [html] (self): |
|
13 |
"""Error""" |
|
14 |
return |
|
15 |
|
|
16 |
def _q_lookup [html] (self, server_name): |
|
17 |
form = ApacheForm() |
|
18 |
generate_html('apache', form.handle()) |
admin/apache_vhost.ptl | ||
---|---|---|
1 |
from quixote.directory import Directory |
|
2 |
|
|
3 |
from template import generate_html |
|
4 |
from apache_form import ApacheForm |
|
5 |
|
|
6 |
class ApacheVhost(Directory): |
|
7 |
""" |
|
8 |
Apache vhost management |
|
9 |
""" |
|
10 |
_q_exports = ['', 'edit'] |
|
11 |
|
|
12 |
def __init__(self, server_name): |
|
13 |
self.server_name = server_name |
|
14 |
|
|
15 |
def _q_index [html] (self): |
|
16 |
"""ok""" |
|
17 |
|
|
18 |
def _q_lookup [html] (self, component): |
|
19 |
component + " " + self.server_name |
|
20 |
|
|
21 |
def edit [html] (self): |
|
22 |
'test' |
admin/css/larpe-admin.css | ||
---|---|---|
1 |
@import url(../wcs-common.css); |
|
2 |
|
|
3 |
html, body { |
|
4 |
margin: 0; |
|
5 |
background: white url(page-bg.png) repeat-y; |
|
6 |
} |
|
7 |
|
|
8 |
div#main-content { |
|
9 |
margin-left: 160px; |
|
10 |
margin-top: -10px; |
|
11 |
margin-right: 20px; |
|
12 |
} |
|
13 |
|
|
14 |
div#main-content h1 { |
|
15 |
color: #006699; |
|
16 |
font-size: 120%; |
|
17 |
} |
|
18 |
|
|
19 |
div#main-content h2 { |
|
20 |
color: #006699; |
|
21 |
font-size: 115%; |
|
22 |
} |
|
23 |
|
|
24 |
div#main-content h3 { |
|
25 |
color: #006699; |
|
26 |
font-size: 108% |
|
27 |
} |
|
28 |
|
|
29 |
|
|
30 |
|
|
31 |
div#header { |
|
32 |
margin: 0; |
|
33 |
background: white url(head-bg.png) repeat-x; |
|
34 |
height: 58px; |
|
35 |
} |
|
36 |
|
|
37 |
ul#menu { |
|
38 |
background: transparent url(head-logo-larpe.png) no-repeat; |
|
39 |
width: 177px; |
|
40 |
margin: 0; |
|
41 |
padding: 80px 0 0 5px; |
|
42 |
} |
|
43 |
|
|
44 |
a { |
|
45 |
color: #0066cc; |
|
46 |
text-decoration: none; |
|
47 |
border-bottom: 1px dotted #ff9900; |
|
48 |
} |
|
49 |
|
|
50 |
p.commands a { |
|
51 |
border: 0; |
|
52 |
} |
|
53 |
|
|
54 |
ul#menu a { |
|
55 |
font-weight: bold; |
|
56 |
} |
|
57 |
|
|
58 |
ul#menu li.active a { |
|
59 |
border-bottom: 1px solid #ff9900; |
|
60 |
} |
|
61 |
|
|
62 |
ul#menu li { |
|
63 |
font-size: 90%; |
|
64 |
margin-bottom: 1em; |
|
65 |
max-width: 130px; |
|
66 |
} |
|
67 |
|
|
68 |
div#footer { |
|
69 |
display: none; |
|
70 |
} |
|
71 |
|
|
72 |
ul.user-info { |
|
73 |
position: absolute; |
|
74 |
margin: 0; |
|
75 |
padding: 0; |
|
76 |
right: 25px; |
|
77 |
top: 13px; |
|
78 |
font-size: 70%; |
|
79 |
font-weight: bold; |
|
80 |
} |
|
81 |
|
|
82 |
ul.user-info li { |
|
83 |
display: inline; |
|
84 |
padding-left: 10px; |
|
85 |
} |
|
86 |
|
|
87 |
/** end of dc2 changes **/ |
|
88 |
|
|
89 |
|
|
90 |
|
|
91 |
ul.biglist { |
|
92 |
margin: 0; |
|
93 |
padding: 0; |
|
94 |
} |
|
95 |
|
|
96 |
ul.biglist li { |
|
97 |
list-style-type: none; |
|
98 |
margin: 4px 0; |
|
99 |
padding: 0 2px; |
|
100 |
border: 1px solid #888; |
|
101 |
background: #ffe; |
|
102 |
clear: both; |
|
103 |
} |
|
104 |
|
|
105 |
ul.biglist li p.details { |
|
106 |
display: block; |
|
107 |
margin: 0; |
|
108 |
color: #555; |
|
109 |
font-size: 80%; |
|
110 |
} |
|
111 |
|
|
112 |
|
|
113 |
ul.biglist li p.commands { |
|
114 |
float: right; |
|
115 |
margin-top: -17px; |
|
116 |
} |
|
117 |
|
|
118 |
ul.biglist li p.commands img { |
|
119 |
padding-right: 5px; |
|
120 |
} |
|
121 |
|
|
122 |
a img { |
|
123 |
border: 0; |
|
124 |
} |
|
125 |
|
|
126 |
td.time { |
|
127 |
text-align: right; |
|
128 |
} |
|
129 |
|
|
130 |
ul.biglist li.disabled, ul.biglist li.disabled p.details { |
|
131 |
color: #999; |
|
132 |
background: #ddd; |
|
133 |
} |
|
134 |
|
|
135 |
|
|
136 |
dl dt { |
|
137 |
margin : 0; |
|
138 |
padding : 0 0 0 0; |
|
139 |
} |
|
140 |
|
|
141 |
dl dd { |
|
142 |
margin : 0.3em 0 1.5em 10px; |
|
143 |
} |
|
144 |
|
|
145 |
|
|
146 |
img.theme-icon { |
|
147 |
float: right; |
|
148 |
margin: -16px 4px 0px 3px; |
|
149 |
border: 1px solid #999; |
|
150 |
} |
|
151 |
|
|
152 |
div#new-field table { |
|
153 |
margin: 0; |
|
154 |
padding: 0; |
|
155 |
} |
|
156 |
|
|
157 |
div#new-field div.widget { |
|
158 |
margin: 0; |
|
159 |
padding: 0; |
|
160 |
} |
|
161 |
|
|
162 |
div#new-field div.buttons { |
|
163 |
margin: 0; |
|
164 |
padding: 0; |
|
165 |
} |
|
166 |
|
|
167 |
div#new-field div.buttons input { |
|
168 |
margin: 0; |
|
169 |
padding: 0; |
|
170 |
} |
|
171 |
|
|
172 |
div#new-field { |
|
173 |
border: 1px solid #888; |
|
174 |
background: #ffe; |
|
175 |
margin: 2em 0 4px 0; |
|
176 |
padding: 0 2px; |
|
177 |
} |
|
178 |
|
|
179 |
div#new-field div.widget { |
|
180 |
} |
|
181 |
|
|
182 |
div#new-field h3 { |
|
183 |
margin: 0; |
|
184 |
font-size: 100%; |
|
185 |
} |
|
186 |
|
|
187 |
div#new-field br { |
|
188 |
display: none; |
|
189 |
} |
|
190 |
|
|
191 |
div#new-field p.commands { |
|
192 |
float: right; |
|
193 |
margin-top: -17px; |
|
194 |
margin-right: 3px; |
|
195 |
} |
|
196 |
|
|
197 |
div.WorkflowStatusWidget { |
|
198 |
border-left: 1px solid black; |
|
199 |
} |
|
200 |
|
|
201 |
p#breadcrumb { |
|
202 |
background: #e6e6e6; |
|
203 |
-moz-border-radius: 6px; |
|
204 |
padding: 3px 8px; |
|
205 |
font-size: 80%; |
|
206 |
border: 1px solid #bfbfbf; |
|
207 |
} |
|
208 |
|
|
209 |
/** steps **/ |
|
210 |
#steps { |
|
211 |
height: 32px; |
|
212 |
margin-bottom: 1em; |
|
213 |
background: #f0f0f0; |
|
214 |
color: #aaa; |
|
215 |
} |
|
216 |
|
|
217 |
#steps ol { |
|
218 |
list-style: none; |
|
219 |
padding: 0 20px; |
|
220 |
} |
|
221 |
|
|
222 |
#steps li { |
|
223 |
display: inline; |
|
224 |
padding-right: 1em; |
|
225 |
display: block; |
|
226 |
float: left; |
|
227 |
width: 30%; |
|
228 |
list-style: none; |
|
229 |
} |
|
230 |
|
|
231 |
#steps ol ul { |
|
232 |
display: none; |
|
233 |
} |
|
234 |
|
|
235 |
#steps span.marker { |
|
236 |
font-size: 26px; |
|
237 |
padding: 2px 9px; |
|
238 |
font-weight: bold; |
|
239 |
color: white; |
|
240 |
text-align: center; |
|
241 |
background: #ddd; |
|
242 |
border: 1px solid #ddd; |
|
243 |
-moz-border-radius: 0.7ex; |
|
244 |
} |
|
245 |
|
|
246 |
#steps li.current span.marker { |
|
247 |
background: #ffa500; |
|
248 |
border: 1px solid #ffc400; |
|
249 |
} |
|
250 |
|
|
251 |
#steps span.label { |
|
252 |
font-size: 90%; |
|
253 |
} |
|
254 |
|
|
255 |
#steps li.current span.label { |
|
256 |
color: black; |
|
257 |
} |
|
258 |
|
|
259 |
#steps ol ul { |
|
260 |
display: none; |
|
261 |
} |
|
262 |
|
|
263 |
|
|
264 |
/** logs **/ |
|
265 |
form#other-log-select { |
|
266 |
margin-top: 2em; |
|
267 |
padding-top: 1em; |
|
268 |
border-top: 1px solid #999; |
|
269 |
} |
|
270 |
|
|
271 |
form#other-log-select select { |
|
272 |
margin: 0 1em; |
|
273 |
} |
|
274 |
|
|
275 |
tr.level-error td { |
|
276 |
border: 1px solid #800; |
|
277 |
background: red; |
|
278 |
} |
|
279 |
|
|
280 |
tr.level-error td.message { |
|
281 |
font-weight: bold; |
|
282 |
} |
|
283 |
|
admin/ezt.py | ||
---|---|---|
1 |
#!/usr/bin/env python |
|
2 |
"""ezt.py -- easy templating |
|
3 |
|
|
4 |
ezt templates are simply text files in whatever format you so desire |
|
5 |
(such as XML, HTML, etc.) which contain directives sprinkled |
|
6 |
throughout. With these directives it is possible to generate the |
|
7 |
dynamic content from the ezt templates. |
|
8 |
|
|
9 |
These directives are enclosed in square brackets. If you are a |
|
10 |
C-programmer, you might be familar with the #ifdef directives of the C |
|
11 |
preprocessor 'cpp'. ezt provides a similar concept. Additionally EZT |
|
12 |
has a 'for' directive, which allows it to iterate (repeat) certain |
|
13 |
subsections of the template according to sequence of data items |
|
14 |
provided by the application. |
|
15 |
|
|
16 |
The final rendering is performed by the method generate() of the Template |
|
17 |
class. Building template instances can either be done using external |
|
18 |
EZT files (convention: use the suffix .ezt for such files): |
|
19 |
|
|
20 |
>>> template = Template("../templates/log.ezt") |
|
21 |
|
|
22 |
or by calling the parse() method of a template instance directly with |
|
23 |
a EZT template string: |
|
24 |
|
|
25 |
>>> template = Template() |
|
26 |
>>> template.parse('''<html><head> |
|
27 |
... <title>[title_string]</title></head> |
|
28 |
... <body><h1>[title_string]</h1> |
|
29 |
... [for a_sequence] <p>[a_sequence]</p> |
|
30 |
... [end] <hr> |
|
31 |
... The [person] is [if-any state]in[else]out[end]. |
|
32 |
... </body> |
|
33 |
... </html> |
|
34 |
... ''') |
|
35 |
|
|
36 |
The application should build a dictionary 'data' and pass it together |
|
37 |
with the output fileobject to the templates generate method: |
|
38 |
|
|
39 |
>>> data = {'title_string' : "A Dummy Page", |
|
40 |
... 'a_sequence' : ['list item 1', 'list item 2', 'another element'], |
|
41 |
... 'person': "doctor", |
|
42 |
... 'state' : None } |
|
43 |
>>> import sys |
|
44 |
>>> template.generate(sys.stdout, data) |
|
45 |
<html><head> |
|
46 |
<title>A Dummy Page</title></head> |
|
47 |
<body><h1>A Dummy Page</h1> |
|
48 |
<p>list item 1</p> |
|
49 |
<p>list item 2</p> |
|
50 |
<p>another element</p> |
|
51 |
<hr> |
|
52 |
The doctor is out. |
|
53 |
</body> |
|
54 |
</html> |
|
55 |
|
|
56 |
Template syntax error reporting should be improved. Currently it is |
|
57 |
very sparse (template line numbers would be nice): |
|
58 |
|
|
59 |
>>> Template().parse("[if-any where] foo [else] bar [end unexpected args]") |
|
60 |
Traceback (innermost last): |
|
61 |
File "<stdin>", line 1, in ? |
|
62 |
File "ezt.py", line 220, in parse |
|
63 |
self.program = self._parse(text) |
|
64 |
File "ezt.py", line 275, in _parse |
|
65 |
raise ArgCountSyntaxError(str(args[1:])) |
|
66 |
ArgCountSyntaxError: ['unexpected', 'args'] |
|
67 |
>>> Template().parse("[if unmatched_end]foo[end]") |
|
68 |
Traceback (innermost last): |
|
69 |
File "<stdin>", line 1, in ? |
|
70 |
File "ezt.py", line 206, in parse |
|
71 |
self.program = self._parse(text) |
|
72 |
File "ezt.py", line 266, in _parse |
|
73 |
raise UnmatchedEndError() |
|
74 |
UnmatchedEndError |
|
75 |
|
|
76 |
|
|
77 |
Directives |
|
78 |
========== |
|
79 |
|
|
80 |
Several directives allow the use of dotted qualified names refering to objects |
|
81 |
or attributes of objects contained in the data dictionary given to the |
|
82 |
.generate() method. |
|
83 |
|
|
84 |
Qualified names |
|
85 |
--------------- |
|
86 |
|
|
87 |
Qualified names have two basic forms: a variable reference, or a string |
|
88 |
constant. References are a name from the data dictionary with optional |
|
89 |
dotted attributes (where each intermediary is an object with attributes, |
|
90 |
of course). |
|
91 |
|
|
92 |
Examples: |
|
93 |
|
|
94 |
[varname] |
|
95 |
|
|
96 |
[ob.attr] |
|
97 |
|
|
98 |
["string"] |
|
99 |
|
|
100 |
Simple directives |
|
101 |
----------------- |
|
102 |
|
|
103 |
[QUAL_NAME] |
|
104 |
|
|
105 |
This directive is simply replaced by the value of the qualified name. |
|
106 |
If the value is a number it's converted to a string before being |
|
107 |
outputted. If it is None, nothing is outputted. If it is a python file |
|
108 |
object (i.e. any object with a "read" method), it's contents are |
|
109 |
outputted. If it is a callback function (any callable python object |
|
110 |
is assumed to be a callback function), it is invoked and passed an EZT |
|
111 |
printer function as an argument. |
|
112 |
|
|
113 |
[QUAL_NAME QUAL_NAME ...] |
|
114 |
|
|
115 |
If the first value is a callback function, it is invoked with the |
|
116 |
output file pointer as a first argument, and the rest of the values as |
|
117 |
additional arguments. |
|
118 |
|
|
119 |
Otherwise, the first value defines a substitution format, specifying |
|
120 |
constant text and indices of the additional arguments. The arguments |
|
121 |
are substituted and the result is inserted into the output stream. |
|
122 |
|
|
123 |
Example: |
|
124 |
["abc %0 def %1 ghi %0" foo bar.baz] |
|
125 |
|
|
126 |
Note that the first value can be any type of qualified name -- a string |
|
127 |
constant or a variable reference. Use %% to substitute a percent sign. |
|
128 |
Argument indices are 0-based. |
|
129 |
|
|
130 |
[include "filename"] or [include QUAL_NAME] |
|
131 |
|
|
132 |
This directive is replaced by content of the named include file. Note |
|
133 |
that a string constant is more efficient -- the target file is compiled |
|
134 |
inline. In the variable form, the target file is compiled and executed |
|
135 |
at runtime. |
|
136 |
|
|
137 |
Block directives |
|
138 |
---------------- |
|
139 |
|
|
140 |
[for QUAL_NAME] ... [end] |
|
141 |
|
|
142 |
The text within the [for ...] directive and the corresponding [end] |
|
143 |
is repeated for each element in the sequence referred to by the |
|
144 |
qualified name in the for directive. Within the for block this |
|
145 |
identifiers now refers to the actual item indexed by this loop |
|
146 |
iteration. |
|
147 |
|
|
148 |
[if-any QUAL_NAME [QUAL_NAME2 ...]] ... [else] ... [end] |
|
149 |
|
|
150 |
Test if any QUAL_NAME value is not None or an empty string or list. |
|
151 |
The [else] clause is optional. CAUTION: Numeric values are |
|
152 |
converted to string, so if QUAL_NAME refers to a numeric value 0, |
|
153 |
the then-clause is substituted! |
|
154 |
|
|
155 |
[if-index INDEX_FROM_FOR odd] ... [else] ... [end] |
|
156 |
[if-index INDEX_FROM_FOR even] ... [else] ... [end] |
|
157 |
[if-index INDEX_FROM_FOR first] ... [else] ... [end] |
|
158 |
[if-index INDEX_FROM_FOR last] ... [else] ... [end] |
|
159 |
[if-index INDEX_FROM_FOR NUMBER] ... [else] ... [end] |
|
160 |
|
|
161 |
These five directives work similar to [if-any], but are only useful |
|
162 |
within a [for ...]-block (see above). The odd/even directives are |
|
163 |
for example useful to choose different background colors for |
|
164 |
adjacent rows in a table. Similar the first/last directives might |
|
165 |
be used to remove certain parts (for example "Diff to previous" |
|
166 |
doesn't make sense, if there is no previous). |
|
167 |
|
|
168 |
[is QUAL_NAME STRING] ... [else] ... [end] |
|
169 |
[is QUAL_NAME QUAL_NAME] ... [else] ... [end] |
|
170 |
|
|
171 |
The [is ...] directive is similar to the other conditional |
|
172 |
directives above. But it allows to compare two value references or |
|
173 |
a value reference with some constant string. |
|
174 |
|
|
175 |
[define VARIABLE] ... [end] |
|
176 |
|
|
177 |
The [define ...] directive allows you to create and modify template |
|
178 |
variables from within the template itself. Essentially, any data |
|
179 |
between inside the [define ...] and its matching [end] will be |
|
180 |
expanded using the other template parsing and output generation |
|
181 |
rules, and then stored as a string value assigned to the variable |
|
182 |
VARIABLE. The new (or changed) variable is then available for use |
|
183 |
with other mechanisms such as [is ...] or [if-any ...], as long as |
|
184 |
they appear later in the template. |
|
185 |
|
|
186 |
[format STRING] ... [end] |
|
187 |
|
|
188 |
The format directive controls how the values substituted into |
|
189 |
templates are escaped before they are put into the output stream. It |
|
190 |
has no effect on the literal text of the templates, only the output |
|
191 |
from [QUAL_NAME ...] directives. STRING can be one of "raw" "html" |
|
192 |
or "xml". The "raw" mode leaves the output unaltered. The "html" and |
|
193 |
"xml" modes escape special characters using entity escapes (like |
|
194 |
" and >) |
|
195 |
""" |
|
196 |
# |
|
197 |
# Copyright (C) 2001-2005 Greg Stein. All Rights Reserved. |
|
198 |
# |
|
199 |
# Redistribution and use in source and binary forms, with or without |
|
200 |
# modification, are permitted provided that the following conditions are |
|
201 |
# met: |
|
202 |
# |
|
203 |
# * Redistributions of source code must retain the above copyright |
|
204 |
# notice, this list of conditions and the following disclaimer. |
|
205 |
# |
|
206 |
# * Redistributions in binary form must reproduce the above copyright |
|
207 |
# notice, this list of conditions and the following disclaimer in the |
|
208 |
# documentation and/or other materials provided with the distribution. |
|
209 |
# |
|
210 |
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS |
|
211 |
# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
|
212 |
# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
|
213 |
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE |
|
214 |
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
|
215 |
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
|
216 |
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
|
217 |
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
|
218 |
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
|
219 |
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
|
220 |
# POSSIBILITY OF SUCH DAMAGE. |
|
221 |
# |
|
222 |
# |
|
223 |
# This software is maintained by Greg and is available at: |
|
224 |
# http://svn.webdav.org/repos/projects/ezt/trunk/ |
|
225 |
# |
|
226 |
|
|
227 |
import string |
|
228 |
import re |
|
229 |
from types import StringType, IntType, FloatType, LongType |
|
230 |
import os |
|
231 |
import cgi |
|
232 |
try: |
|
233 |
import cStringIO |
|
234 |
except ImportError: |
|
235 |
import StringIO |
|
236 |
cStringIO = StringIO |
|
237 |
|
|
238 |
# |
|
239 |
# Formatting types |
|
240 |
# |
|
241 |
FORMAT_RAW = 'raw' |
|
242 |
FORMAT_HTML = 'html' |
|
243 |
FORMAT_XML = 'xml' |
|
244 |
|
|
245 |
# |
|
246 |
# This regular expression matches three alternatives: |
|
247 |
# expr: DIRECTIVE | BRACKET | COMMENT |
|
248 |
# DIRECTIVE: '[' ITEM (whitespace ITEM)* '] |
|
249 |
# ITEM: STRING | NAME |
|
250 |
# STRING: '"' (not-slash-or-dquote | '\' anychar)* '"' |
|
251 |
# NAME: (alphanum | '_' | '-' | '.')+ |
|
252 |
# BRACKET: '[[]' |
|
253 |
# COMMENT: '[#' not-rbracket* ']' |
|
254 |
# |
|
255 |
# When used with the split() method, the return value will be composed of |
|
256 |
# non-matching text and the two paren groups (DIRECTIVE and BRACKET). Since |
|
257 |
# the COMMENT matches are not placed into a group, they are considered a |
|
258 |
# "splitting" value and simply dropped. |
|
259 |
# |
|
260 |
_item = r'(?:"(?:[^\\"]|\\.)*"|[-\w.]+)' |
|
261 |
_re_parse = re.compile(r'\[(%s(?: +%s)*)\]|(\[\[\])|\[#[^\]]*\]' % (_item, _item)) |
|
262 |
|
|
263 |
_re_args = re.compile(r'"(?:[^\\"]|\\.)*"|[-\w.]+') |
|
264 |
|
|
265 |
# block commands and their argument counts |
|
266 |
_block_cmd_specs = { 'if-index':2, 'for':1, 'is':2, 'define':1, 'format':1 } |
|
267 |
_block_cmds = _block_cmd_specs.keys() |
|
268 |
|
|
269 |
# two regular expresssions for compressing whitespace. the first is used to |
|
270 |
# compress any whitespace including a newline into a single newline. the |
|
271 |
# second regex is used to compress runs of whitespace into a single space. |
|
272 |
_re_newline = re.compile('[ \t\r\f\v]*\n\\s*') |
|
273 |
_re_whitespace = re.compile(r'\s\s+') |
|
274 |
|
|
275 |
# this regex is used to substitute arguments into a value. we split the value, |
|
276 |
# replace the relevant pieces, and then put it all back together. splitting |
|
277 |
# will produce a list of: TEXT ( splitter TEXT )*. splitter will be '%' or |
|
278 |
# an integer. |
|
279 |
_re_subst = re.compile('%(%|[0-9]+)') |
|
280 |
|
|
281 |
class Template: |
|
282 |
|
|
283 |
_printers = { |
|
284 |
FORMAT_RAW : '_cmd_print', |
|
285 |
FORMAT_HTML : '_cmd_print_html', |
|
286 |
FORMAT_XML : '_cmd_print_xml', |
|
287 |
} |
|
288 |
|
|
289 |
def __init__(self, fname=None, compress_whitespace=1, |
|
290 |
base_format=FORMAT_RAW): |
|
291 |
self.compress_whitespace = compress_whitespace |
|
292 |
if fname: |
|
293 |
self.parse_file(fname, base_format) |
|
294 |
|
|
295 |
def parse_file(self, fname, base_format=FORMAT_RAW): |
|
296 |
"fname -> a string object with pathname of file containg an EZT template." |
|
297 |
|
|
298 |
self.parse(_FileReader(fname), base_format) |
|
299 |
|
|
300 |
def parse(self, text_or_reader, base_format=FORMAT_RAW): |
|
301 |
"""Parse the template specified by text_or_reader. |
|
302 |
|
|
303 |
The argument should be a string containing the template, or it should |
|
304 |
specify a subclass of ezt.Reader which can read templates. The base |
|
305 |
format for printing values is given by base_format. |
|
306 |
""" |
|
307 |
if not isinstance(text_or_reader, Reader): |
|
308 |
# assume the argument is a plain text string |
|
309 |
text_or_reader = _TextReader(text_or_reader) |
|
310 |
|
|
311 |
printer = getattr(self, self._printers[base_format]) |
|
312 |
self.program = self._parse(text_or_reader, base_printer=printer) |
|
313 |
|
|
314 |
def generate(self, fp, data): |
|
315 |
if hasattr(data, '__getitem__') or callable(getattr(data, 'keys', None)): |
|
316 |
# a dictionary-like object was passed. convert it to an |
|
317 |
# attribute-based object. |
|
318 |
class _data_ob: |
|
319 |
def __init__(self, d): |
|
320 |
vars(self).update(d) |
|
321 |
data = _data_ob(data) |
|
322 |
|
|
323 |
ctx = _context() |
|
324 |
ctx.data = data |
|
325 |
ctx.for_index = { } |
|
326 |
ctx.defines = { } |
|
327 |
self._execute(self.program, fp, ctx) |
|
328 |
|
|
329 |
def _parse(self, reader, for_names=None, file_args=(), base_printer=None): |
|
330 |
"""text -> string object containing the template. |
|
331 |
|
|
332 |
This is a private helper function doing the real work for method parse. |
|
333 |
It returns the parsed template as a 'program'. This program is a sequence |
|
334 |
made out of strings or (function, argument) 2-tuples. |
|
335 |
|
|
336 |
Note: comment directives [# ...] are automatically dropped by _re_parse. |
|
337 |
""" |
|
338 |
|
|
339 |
# parse the template program into: (TEXT DIRECTIVE BRACKET)* TEXT |
|
340 |
parts = _re_parse.split(reader.text) |
|
341 |
|
|
342 |
program = [ ] |
|
343 |
stack = [ ] |
|
344 |
if not for_names: |
|
345 |
for_names = [ ] |
|
346 |
|
|
347 |
if base_printer: |
|
348 |
printers = [ base_printer ] |
|
349 |
else: |
|
350 |
printers = [ self._cmd_print ] |
|
351 |
|
|
352 |
for i in range(len(parts)): |
|
353 |
piece = parts[i] |
|
354 |
which = i % 3 # discriminate between: TEXT DIRECTIVE BRACKET |
|
355 |
if which == 0: |
|
356 |
# TEXT. append if non-empty. |
|
357 |
if piece: |
|
358 |
if self.compress_whitespace: |
|
359 |
piece = _re_whitespace.sub(' ', _re_newline.sub('\n', piece)) |
|
360 |
program.append(piece) |
|
361 |
elif which == 2: |
|
362 |
# BRACKET directive. append '[' if present. |
|
363 |
if piece: |
|
364 |
program.append('[') |
|
365 |
elif piece: |
|
366 |
# DIRECTIVE is present. |
|
367 |
args = _re_args.findall(piece) |
|
368 |
cmd = args[0] |
|
369 |
if cmd == 'else': |
|
370 |
if len(args) > 1: |
|
371 |
raise ArgCountSyntaxError(str(args[1:])) |
|
372 |
### check: don't allow for 'for' cmd |
|
373 |
idx = stack[-1][1] |
|
374 |
true_section = program[idx:] |
|
375 |
del program[idx:] |
|
376 |
stack[-1][3] = true_section |
|
377 |
elif cmd == 'end': |
|
378 |
if len(args) > 1: |
|
379 |
raise ArgCountSyntaxError(str(args[1:])) |
|
380 |
# note: true-section may be None |
|
381 |
try: |
|
382 |
cmd, idx, args, true_section = stack.pop() |
|
383 |
except IndexError: |
|
384 |
raise UnmatchedEndError() |
|
385 |
else_section = program[idx:] |
|
386 |
if cmd == 'format': |
|
387 |
printers.pop() |
|
388 |
else: |
|
389 |
func = getattr(self, '_cmd_' + re.sub('-', '_', cmd)) |
|
390 |
program[idx:] = [ (func, (args, true_section, else_section)) ] |
|
391 |
if cmd == 'for': |
|
392 |
for_names.pop() |
|
393 |
elif cmd in _block_cmds: |
|
394 |
if len(args) > _block_cmd_specs[cmd] + 1: |
|
395 |
raise ArgCountSyntaxError(str(args[1:])) |
|
396 |
### this assumes arg1 is always a ref unless cmd is 'define' |
|
397 |
if cmd != 'define': |
|
398 |
args[1] = _prepare_ref(args[1], for_names, file_args) |
|
399 |
|
|
400 |
# handle arg2 for the 'is' command |
|
401 |
if cmd == 'is': |
|
402 |
args[2] = _prepare_ref(args[2], for_names, file_args) |
|
403 |
elif cmd == 'for': |
|
404 |
for_names.append(args[1][0]) # append the refname |
|
405 |
elif cmd == 'format': |
|
406 |
if args[1][0]: |
|
407 |
raise BadFormatConstantError(str(args[1:])) |
|
408 |
funcname = self._printers.get(args[1][1]) |
|
409 |
if not funcname: |
|
410 |
raise UnknownFormatConstantError(str(args[1:])) |
|
411 |
printers.append(getattr(self, funcname)) |
|
412 |
|
|
413 |
# remember the cmd, current pos, args, and a section placeholder |
|
414 |
stack.append([cmd, len(program), args[1:], None]) |
|
415 |
elif cmd == 'include': |
|
416 |
if args[1][0] == '"': |
|
417 |
include_filename = args[1][1:-1] |
|
418 |
f_args = [ ] |
|
419 |
for arg in args[2:]: |
|
420 |
f_args.append(_prepare_ref(arg, for_names, file_args)) |
|
421 |
program.extend(self._parse(reader.read_other(include_filename), |
|
422 |
for_names, f_args, printers[-1])) |
|
423 |
else: |
|
424 |
if len(args) != 2: |
|
425 |
raise ArgCountSyntaxError(str(args)) |
|
426 |
program.append((self._cmd_include, |
|
427 |
(_prepare_ref(args[1], for_names, file_args), |
|
428 |
reader))) |
|
429 |
elif cmd == 'if-any': |
|
430 |
f_args = [ ] |
|
431 |
for arg in args[1:]: |
|
432 |
f_args.append(_prepare_ref(arg, for_names, file_args)) |
|
433 |
stack.append(['if-any', len(program), f_args, None]) |
|
434 |
else: |
|
435 |
# implied PRINT command |
|
436 |
f_args = [ ] |
|
437 |
for arg in args: |
|
438 |
f_args.append(_prepare_ref(arg, for_names, file_args)) |
|
439 |
program.append((printers[-1], f_args)) |
|
440 |
|
|
441 |
if stack: |
|
442 |
### would be nice to say which blocks... |
|
443 |
raise UnclosedBlocksError() |
|
444 |
return program |
|
445 |
|
|
446 |
def _execute(self, program, fp, ctx): |
|
447 |
"""This private helper function takes a 'program' sequence as created |
|
448 |
by the method '_parse' and executes it step by step. strings are written |
|
449 |
to the file object 'fp' and functions are called. |
|
450 |
""" |
|
451 |
for step in program: |
|
452 |
if isinstance(step, StringType): |
|
453 |
fp.write(step) |
|
454 |
else: |
|
455 |
step[0](step[1], fp, ctx) |
|
456 |
|
|
457 |
def _cmd_print(self, valref, fp, ctx): |
|
458 |
_write_value(valref, fp, ctx) |
|
459 |
|
|
460 |
def _cmd_print_html(self, valref, fp, ctx): |
|
461 |
_write_value(valref, fp, ctx, cgi.escape) |
|
462 |
|
|
463 |
def _cmd_print_xml(self, valref, fp, ctx): |
|
464 |
### use the same quoting as HTML for now |
|
465 |
self._cmd_print_html(valref, fp, ctx) |
|
466 |
|
|
467 |
def _cmd_include(self, (valref, reader), fp, ctx): |
|
468 |
fname = _get_value(valref, ctx) |
|
469 |
### note: we don't have the set of for_names to pass into this parse. |
|
470 |
### I don't think there is anything to do but document it. we also |
|
471 |
### don't have a current format (since that is a compile-time concept). |
|
472 |
self._execute(self._parse(reader.read_other(fname)), fp, ctx) |
|
473 |
|
|
474 |
def _cmd_if_any(self, args, fp, ctx): |
|
475 |
"If any value is a non-empty string or non-empty list, then T else F." |
|
476 |
(valrefs, t_section, f_section) = args |
|
477 |
value = 0 |
|
478 |
for valref in valrefs: |
|
479 |
if _get_value(valref, ctx): |
|
480 |
value = 1 |
|
481 |
break |
|
482 |
self._do_if(value, t_section, f_section, fp, ctx) |
|
483 |
|
|
484 |
def _cmd_if_index(self, args, fp, ctx): |
|
485 |
((valref, value), t_section, f_section) = args |
|
486 |
list, idx = ctx.for_index[valref[0]] |
|
487 |
if value == 'even': |
|
488 |
value = idx % 2 == 0 |
|
489 |
elif value == 'odd': |
|
490 |
value = idx % 2 == 1 |
|
491 |
elif value == 'first': |
|
492 |
value = idx == 0 |
|
493 |
elif value == 'last': |
|
494 |
value = idx == len(list)-1 |
|
495 |
else: |
|
496 |
value = idx == int(value) |
|
497 |
self._do_if(value, t_section, f_section, fp, ctx) |
|
498 |
|
|
499 |
def _cmd_is(self, args, fp, ctx): |
|
500 |
((left_ref, right_ref), t_section, f_section) = args |
|
501 |
value = _get_value(right_ref, ctx) |
|
502 |
value = string.lower(_get_value(left_ref, ctx)) == string.lower(value) |
|
503 |
self._do_if(value, t_section, f_section, fp, ctx) |
|
504 |
|
|
505 |
def _do_if(self, value, t_section, f_section, fp, ctx): |
|
506 |
if t_section is None: |
|
507 |
t_section = f_section |
|
508 |
f_section = None |
|
509 |
if value: |
|
510 |
section = t_section |
|
511 |
else: |
|
512 |
section = f_section |
|
513 |
if section is not None: |
|
514 |
self._execute(section, fp, ctx) |
|
515 |
|
|
516 |
def _cmd_for(self, args, fp, ctx): |
|
517 |
((valref,), unused, section) = args |
|
518 |
list = _get_value(valref, ctx) |
|
519 |
if isinstance(list, StringType): |
|
520 |
raise NeedSequenceError() |
|
521 |
refname = valref[0] |
|
522 |
ctx.for_index[refname] = idx = [ list, 0 ] |
|
523 |
for item in list: |
|
524 |
self._execute(section, fp, ctx) |
|
525 |
idx[1] = idx[1] + 1 |
|
526 |
del ctx.for_index[refname] |
|
527 |
|
|
528 |
def _cmd_define(self, args, fp, ctx): |
|
529 |
((name,), unused, section) = args |
|
530 |
valfp = cStringIO.StringIO() |
|
531 |
if section is not None: |
|
532 |
self._execute(section, valfp, ctx) |
|
533 |
ctx.defines[name] = valfp.getvalue() |
|
534 |
|
|
535 |
def boolean(value): |
|
536 |
"Return a value suitable for [if-any bool_var] usage in a template." |
|
537 |
if value: |
|
538 |
return 'yes' |
|
539 |
return None |
|
540 |
|
|
541 |
|
|
542 |
def _prepare_ref(refname, for_names, file_args): |
|
543 |
"""refname -> a string containing a dotted identifier. example:"foo.bar.bang" |
|
544 |
for_names -> a list of active for sequences. |
|
545 |
|
|
546 |
Returns a `value reference', a 3-tuple made out of (refname, start, rest), |
|
547 |
for fast access later. |
|
548 |
""" |
|
549 |
# is the reference a string constant? |
|
550 |
if refname[0] == '"': |
|
551 |
return None, refname[1:-1], None |
|
552 |
|
|
553 |
parts = string.split(refname, '.') |
|
554 |
start = parts[0] |
|
555 |
rest = parts[1:] |
|
556 |
|
|
557 |
# if this is an include-argument, then just return the prepared ref |
|
558 |
if start[:3] == 'arg': |
|
559 |
try: |
|
560 |
idx = int(start[3:]) |
|
561 |
except ValueError: |
|
562 |
pass |
|
563 |
else: |
|
564 |
if idx < len(file_args): |
|
565 |
orig_refname, start, more_rest = file_args[idx] |
|
566 |
if more_rest is None: |
|
567 |
# the include-argument was a string constant |
|
568 |
return None, start, None |
|
569 |
|
|
570 |
# prepend the argument's "rest" for our further processing |
|
571 |
rest[:0] = more_rest |
|
572 |
|
|
573 |
# rewrite the refname to ensure that any potential 'for' processing |
|
574 |
# has the correct name |
|
575 |
### this can make it hard for debugging include files since we lose |
|
576 |
### the 'argNNN' names |
|
577 |
if not rest: |
|
578 |
return start, start, [ ] |
|
579 |
refname = start + '.' + string.join(rest, '.') |
|
580 |
|
|
581 |
if for_names: |
|
582 |
# From last to first part, check if this reference is part of a for loop |
|
583 |
for i in range(len(parts), 0, -1): |
|
584 |
name = string.join(parts[:i], '.') |
|
585 |
if name in for_names: |
|
586 |
return refname, name, parts[i:] |
|
587 |
|
|
588 |
return refname, start, rest |
|
589 |
|
|
590 |
def _get_value((refname, start, rest), ctx): |
|
591 |
"""(refname, start, rest) -> a prepared `value reference' (see above). |
|
592 |
ctx -> an execution context instance. |
|
593 |
|
|
594 |
Does a name space lookup within the template name space. Active |
|
595 |
for blocks take precedence over data dictionary members with the |
|
596 |
same name. |
|
597 |
""" |
|
598 |
if rest is None: |
|
599 |
# it was a string constant |
|
600 |
return start |
|
601 |
|
|
602 |
# get the starting object |
|
603 |
if ctx.for_index.has_key(start): |
|
604 |
list, idx = ctx.for_index[start] |
|
605 |
ob = list[idx] |
|
606 |
elif ctx.defines.has_key(start): |
|
607 |
ob = ctx.defines[start] |
|
608 |
elif hasattr(ctx.data, start): |
|
609 |
ob = getattr(ctx.data, start) |
|
610 |
else: |
|
611 |
raise UnknownReference(refname) |
|
612 |
|
|
613 |
# walk the rest of the dotted reference |
|
614 |
for attr in rest: |
|
615 |
try: |
|
616 |
ob = getattr(ob, attr) |
|
617 |
except AttributeError: |
|
618 |
raise UnknownReference(refname) |
|
619 |
|
|
620 |
# make sure we return a string instead of some various Python types |
|
621 |
if isinstance(ob, IntType) \ |
|
622 |
or isinstance(ob, LongType) \ |
|
623 |
or isinstance(ob, FloatType): |
|
624 |
return str(ob) |
|
625 |
if ob is None: |
|
626 |
return '' |
|
627 |
|
|
628 |
# string or a sequence |
|
629 |
return ob |
|
630 |
|
|
631 |
def _write_value(valrefs, fp, ctx, format=lambda s: s): |
|
632 |
value = _get_value(valrefs[0], ctx) |
|
633 |
args = map(lambda valref, ctx=ctx: _get_value(valref, ctx), valrefs[1:]) |
|
634 |
|
|
635 |
# if the value has a 'read' attribute, then it is a stream: copy it |
|
636 |
if hasattr(value, 'read'): |
|
637 |
while 1: |
|
638 |
chunk = value.read(16384) |
|
639 |
if not chunk: |
|
640 |
break |
|
641 |
fp.write(format(chunk)) |
|
642 |
|
|
643 |
# value is a callback function: call with file pointer and extra args |
|
644 |
elif callable(value): |
|
645 |
apply(value, [fp] + args) |
|
646 |
|
|
647 |
# value is a substitution pattern |
|
648 |
elif args: |
|
649 |
parts = _re_subst.split(value) |
|
650 |
for i in range(len(parts)): |
|
651 |
piece = parts[i] |
|
652 |
if i%2 == 1 and piece != '%': |
|
653 |
idx = int(piece) |
|
654 |
if idx < len(args): |
|
655 |
piece = args[idx] |
|
656 |
else: |
|
657 |
piece = '<undef>' |
|
658 |
if format: |
|
659 |
fp.write(format(piece)) |
|
660 |
|
|
661 |
# plain old value, write to output |
|
662 |
else: |
|
663 |
fp.write(format(value)) |
|
664 |
|
|
665 |
|
|
666 |
class _context: |
|
667 |
"""A container for the execution context""" |
|
668 |
|
|
669 |
|
|
670 |
class Reader: |
|
671 |
"Abstract class which allows EZT to detect Reader objects." |
|
672 |
|
|
673 |
class _FileReader(Reader): |
|
674 |
"""Reads templates from the filesystem.""" |
|
675 |
def __init__(self, fname): |
|
676 |
self.text = open(fname, 'rb').read() |
|
677 |
self._dir = os.path.dirname(fname) |
|
678 |
def read_other(self, relative): |
|
679 |
return _FileReader(os.path.join(self._dir, relative)) |
|
680 |
|
|
681 |
class _TextReader(Reader): |
|
682 |
"""'Reads' a template from provided text.""" |
|
683 |
def __init__(self, text): |
|
684 |
self.text = text |
|
685 |
def read_other(self, relative): |
|
686 |
raise BaseUnavailableError() |
|
687 |
|
|
688 |
|
|
689 |
class EZTException(Exception): |
|
690 |
"""Parent class of all EZT exceptions.""" |
|
691 |
|
|
692 |
class ArgCountSyntaxError(EZTException): |
|
693 |
"""A bracket directive got the wrong number of arguments.""" |
|
694 |
|
|
695 |
class UnknownReference(EZTException): |
|
696 |
"""The template references an object not contained in the data dictionary.""" |
|
697 |
|
|
698 |
class NeedSequenceError(EZTException): |
|
699 |
"""The object dereferenced by the template is no sequence (tuple or list).""" |
|
700 |
|
|
701 |
class UnclosedBlocksError(EZTException): |
|
702 |
"""This error may be simply a missing [end].""" |
|
703 |
|
|
704 |
class UnmatchedEndError(EZTException): |
|
705 |
"""This error may be caused by a misspelled if directive.""" |
|
706 |
|
|
707 |
class BaseUnavailableError(EZTException): |
|
708 |
"""Base location is unavailable, which disables includes.""" |
|
709 |
|
|
710 |
class BadFormatConstantError(EZTException): |
|
711 |
"""Format specifiers must be string constants.""" |
|
712 |
|
|
713 |
class UnknownFormatConstantError(EZTException): |
|
714 |
"""The format specifier is an unknown value.""" |
|
715 |
|
|
716 |
|
|
717 |
# --- standard test environment --- |
|
718 |
def test_parse(): |
|
719 |
assert _re_parse.split('[a]') == ['', '[a]', None, ''] |
|
720 |
assert _re_parse.split('[a] [b]') == \ |
|
721 |
['', '[a]', None, ' ', '[b]', None, ''] |
|
722 |
assert _re_parse.split('[a c] [b]') == \ |
|
723 |
['', '[a c]', None, ' ', '[b]', None, ''] |
|
724 |
assert _re_parse.split('x [a] y [b] z') == \ |
|
725 |
['x ', '[a]', None, ' y ', '[b]', None, ' z'] |
|
726 |
assert _re_parse.split('[a "b" c "d"]') == \ |
|
727 |
['', '[a "b" c "d"]', None, ''] |
|
728 |
assert _re_parse.split(r'["a \"b[foo]" c.d f]') == \ |
|
729 |
['', '["a \\"b[foo]" c.d f]', None, ''] |
|
730 |
|
|
731 |
def _test(argv): |
|
732 |
import doctest, ezt |
|
733 |
verbose = "-v" in argv |
|
734 |
return doctest.testmod(ezt, verbose=verbose) |
|
735 |
|
|
736 |
if __name__ == "__main__": |
|
737 |
# invoke unit test for this module: |
|
738 |
import sys |
|
739 |
sys.exit(_test(sys.argv)[0]) |
admin/menu.ptl | ||
---|---|---|
1 |
import os |
|
2 |
import quixote |
|
3 |
from quixote import get_response |
|
4 |
#from wcs import storage |
|
5 |
#from wcs import misc |
|
6 |
#from wcs.users import User |
|
7 |
|
|
8 |
items = [ |
|
9 |
('apache', 'Apache')] |
|
10 |
|
|
11 |
# ('forms', N_('Forms')), |
|
12 |
#('consultations', N_('Consultations')), |
|
13 |
# ('workflows', N_('Workflows')), |
|
14 |
# ('users', N_('Users')), |
|
15 |
# ('roles', N_('Roles')), |
|
16 |
# ('categories', N_('Categories')), |
|
17 |
# ('logger', N_('Logs')), |
|
18 |
# ('settings', N_('Settings')), |
|
19 |
# ('/', N_('WCS Form Server'))] |
|
20 |
|
|
21 |
def generate_header_menu [html] (selected = None): |
|
22 |
s = ["""<ul id="menu">\n"""] |
|
23 |
base_url = quixote.get_request().environ['SCRIPT_NAME'] + '/admin' |
|
24 |
# features = misc.cfg.get('misc', {}).get('features', 'both') |
|
25 |
# show_logger = misc.cfg.get('debug', {}).get('logger', False) |
|
26 |
for k, v in items: |
|
27 |
if k == '/': |
|
28 |
continue # skip root |
|
29 |
# if k == 'logger' and not show_logger: |
|
30 |
# continue |
|
31 |
# if features == 'forms' and k == 'consultations': |
|
32 |
# continue |
|
33 |
# if features == 'consultations' and k == 'forms': |
|
34 |
# continue |
|
35 |
if k == selected: |
|
36 |
s.append('<li class="active">') |
|
37 |
else: |
|
38 |
s.append('<li>') |
|
39 |
s.append('<a href="%s/%s/">%s</a></li>\n' % (base_url, k, v)) |
|
40 |
s.append('</ul>\n') |
|
41 |
return ''.join(s) |
|
42 |
|
|
43 |
#def generate_user_info [html] (): |
|
44 |
# session = quixote.get_session() |
|
45 |
# if not session or not session.user: |
|
46 |
# return '' |
|
47 |
# try: |
|
48 |
# user = User.get(session.user) |
|
49 |
# username = user.name |
|
50 |
# except KeyError: |
|
51 |
# username = _('Unknown') |
|
52 |
# logout_url = quixote.get_request().environ['SCRIPT_NAME'] + '/logout' |
|
53 |
# """<ul class="user-info"> |
|
54 |
# <li class="ui-name">%s</li> |
|
55 |
# <li class="ui-logout"><a href="%s">%s</a></li> |
|
56 |
#</ul>""" % (username, logout_url, _('logout')) |
|
57 |
|
|
58 |
|
|
59 |
def html_top [html] (section, title = None, scripts = None): |
|
60 |
header_menu = generate_header_menu(section) |
|
61 |
# user_info = generate_user_info() |
|
62 |
subtitle = '' |
|
63 |
for s in items: |
|
64 |
if s[0] == section: |
|
65 |
subtitle = _(s[1]) |
|
66 |
if not title: |
|
67 |
title = '' |
|
68 |
else: |
|
69 |
title = ' - ' + title |
|
70 |
if not scripts: |
|
71 |
scripts = '' |
|
72 |
else: |
|
73 |
scripts = '\n'.join(['<script src="%s" type="text/javascript"></script>' % x for x in scripts]) |
|
74 |
|
|
75 |
sitetitle = 'Larpe Administration' |
|
76 |
if title: |
|
77 |
sitetitle += ' - ' |
|
78 |
|
|
79 |
admin_ezt = True |
|
80 |
|
|
81 |
|
|
82 |
|
|
83 |
""" |
|
84 |
<?xml version="1.0" encoding="utf-8"?> |
|
85 |
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" |
|
86 |
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> |
|
87 |
<html xmlns="http://www.w3.org/1999/xhtml"> |
|
88 |
<head> |
|
89 |
<title>Administration de Larpe</title> |
|
90 |
<link rel="stylesheet" type="text/css" href="/css/larpe-admin.css"/> |
|
91 |
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> |
|
92 |
</head> |
|
93 |
<body> |
|
94 |
<div id="header">%s |
|
95 |
</div> |
|
96 |
<div id="main-content"> |
|
97 |
<p id="breadcrumb"><a href="/">Administration</a></p> |
|
98 |
|
|
99 |
<h1>Relai inverse Liberty Alliance</h1> |
|
100 |
|
|
101 |
</div> |
|
102 |
<div id="footer"> |
|
103 |
<p id="lasso">Powered by Lasso</p> |
|
104 |
</div> |
|
105 |
</body> |
|
106 |
</html> |
|
107 |
""" % header_menu |
|
108 |
|
|
109 |
#<ul id="menu"> |
|
110 |
#<li><a href="/admin/apache/">Apache</a></li> |
|
111 |
#</ul> |
|
112 |
#</div> |
|
113 |
#<div id="main-content"> |
|
114 |
|
|
115 |
|
|
116 |
# get_response(). |
|
117 |
# get_response().filter.update(locals()) |
|
118 |
|
|
119 |
def error_page [html] (section, error): |
|
120 |
html_top(section, title = _('Error')) |
|
121 |
'<div id="error-page">' |
|
122 |
'<h2>%s</h2>' % _('Error') |
|
123 |
'<p>%s</p>' % error |
|
124 |
'</div>' |
|
125 |
|
|
126 |
#def command_icon [html] (url, type, label = None, icon = None): |
|
127 |
# icons = { |
|
128 |
# 'edit': 'stock_edit_16.png', |
|
129 |
# 'add': 'stock_add_16.png', |
|
130 |
# 'remove': 'stock_remove_16.png', |
|
131 |
# 'duplicate': 'stock_copy_16.png', |
|
132 |
# 'view': 'view_16.png', |
|
133 |
# } |
|
134 |
# labels = { |
|
135 |
# 'add': N_('Add'), |
|
136 |
# 'edit': N_('Edit'), |
|
137 |
# 'remove': N_('Remove'), |
|
138 |
# 'duplicate': N_('Duplicate'), |
|
139 |
# 'view': N_('View'), |
|
140 |
# } |
|
141 |
# if not label: |
|
142 |
# label = _(labels[str(type)]) |
|
143 |
# if not icon: |
|
144 |
# icon = icons[str(type)] |
|
145 |
# if url: |
|
146 |
# '''<span class="%(type)s"> |
|
147 |
# <a href="%(url)s"><img src="/images/%(icon)s" alt="%(label)s" title="%(label)s" /></a> |
|
148 |
#</span>''' % locals() |
|
149 |
# else: |
|
150 |
# # no url -> image button |
|
151 |
# '''<span class="%(type)s"> |
|
152 |
# <input type="image" src="/images/%(icon)s" alt="%(label)s" title="%(label)s" /> |
|
153 |
#</span>''' % locals() |
admin/root.ptl | ||
---|---|---|
1 |
from quixote.directory import Directory |
|
2 |
|
|
3 |
#from menu import html_top |
|
4 |
from template import generate_html |
|
5 |
from apache import Apache |
|
6 |
|
|
7 |
class RootDirectory(Directory): |
|
8 |
_q_exports = ['', 'apache'] |
|
9 |
|
|
10 |
apache = Apache() |
|
11 |
|
|
12 |
def _q_index [html] (self): |
|
13 |
text = """<p>Choisissez dans le menu ce que vous souhaitez administrer</p>""" |
|
14 |
generate_html(None, text) |
|
15 |
#html_top('/') |
admin/template.ptl | ||
---|---|---|
1 |
from cStringIO import StringIO |
|
2 |
|
|
3 |
from quixote.html import htmltext |
|
4 |
|
|
5 |
import ezt |
|
6 |
|
|
7 |
#<?xml version="1.0" encoding="utf-8"?> |
|
8 |
# <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> |
|
9 |
|
|
10 |
|
|
11 |
ADMIN_TEMPLATE_EZT = """<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" |
|
12 |
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> |
|
13 |
<html xmlns="http://www.w3.org/1999/xhtml"> |
|
14 |
<head> |
|
15 |
<title>Administration de Larpe - [page_title]</title> |
|
16 |
<link rel="stylesheet" type="text/css" href="/css/larpe-admin.css"/> |
|
17 |
</head> |
|
18 |
<body> |
|
19 |
<div id="header">[main_menu]</div> |
|
20 |
<div id="main-content"> |
|
21 |
<p id="breadcrumb">[breadcrumb]</p> |
|
22 |
<h1>[subtitle]</h1> |
|
23 |
|
|
24 |
[body] |
|
25 |
|
|
26 |
</div> |
|
27 |
<div id="footer"> |
|
28 |
<p id="lasso">Powered by Lasso</p> |
|
29 |
</div> |
|
30 |
</body> |
|
31 |
</html>""" |
|
32 |
|
|
33 |
admin_template = ezt.Template() |
|
34 |
admin_template.parse(ADMIN_TEMPLATE_EZT) |
|
35 |
|
|
36 |
menu_items = [ |
|
37 |
('apache', 'Apache')] |
|
38 |
|
|
39 |
def generate_main_menu(selected = None): |
|
40 |
s = '<ul id="menu">\n' |
|
41 |
for k, v in menu_items: |
|
42 |
if k == selected: |
|
43 |
s += '<li class="active">' |
|
44 |
else: |
|
45 |
s += '<li>' |
|
46 |
s += '<a href="/%s/">%s</a></li>\n' % (k, v) |
|
47 |
s + '</ul>\n' |
|
48 |
return s |
|
49 |
|
|
50 |
def generate_breadcrumb(section = None): |
|
51 |
s = '<a href="/">Administration</a>' |
|
52 |
if section is not None: |
|
53 |
for k, v in menu_items: |
|
54 |
if k == section: |
|
55 |
s += ' > <a href="/%s">%s</a>' % (k, v) |
|
56 |
break |
|
57 |
return s |
|
58 |
|
|
59 |
def generate_html(section, body): |
|
60 |
if section is not None: |
|
61 |
section = str(section) |
|
62 |
body = str(body) |
|
63 |
main_menu = generate_main_menu(section) |
|
64 |
breadcrumb = generate_breadcrumb(section) |
|
65 |
page_title = '' |
|
66 |
if section is not None: |
|
67 |
for k, v in menu_items: |
|
68 |
if k == section: |
|
69 |
page_title = v |
|
70 |
break |
|
71 |
subtitle = page_title |
|
72 |
|
|
73 |
fd = StringIO() |
|
74 |
admin_template.generate(fd, locals()) |
|
75 |
return htmltext(fd.getvalue()) |
doc/vhost-eo | ||
---|---|---|
1 |
<VirtualHost 127.0.0.6:80> |
|
2 |
ServerName entrouvert.test.org |
|
3 |
ServerAdmin dlaniel@entrouvert.com |
|
4 |
|
|
5 |
SetOutputFilter proxy-html |
|
6 |
|
|
7 |
<Location /> |
|
8 |
ProxyPass http://www.entrouvert.com/ |
|
9 |
ProxyPassReverse http://www.entrouvert.com/ |
|
10 |
# ProxyPassReverse / |
|
11 |
# ProxyHTMLURLMap / /eo/ |
|
12 |
</Location> |
|
13 |
|
|
14 |
</VirtualHost> |
doc/vhost-rp | ||
---|---|---|
1 |
NameVirtualHost 127.0.0.6:80 |
|
2 |
<VirtualHost 127.0.0.6:80> |
|
3 |
ServerName test.org |
|
4 |
# ServerAdmin dlaniel@entrouvert.com |
|
5 |
|
|
6 |
# LoadFile /usr/lib/libxml2.so.2 |
|
7 |
SetOutputFilter proxy-html |
|
8 |
|
|
9 |
# ProxyPass /eo/ http://www.entrouvert.com/ |
|
10 |
# ProxyPassReverse /eo/ http://www.entrouvert.com/ |
|
11 |
|
|
12 |
<Location /eo/> |
|
13 |
ProxyPass http://www.entrouvert.com/ |
|
14 |
ProxyPassReverse http://www.entrouvert.com/ |
|
15 |
# ProxyPassReverse / |
|
16 |
ProxyHTMLURLMap / /eo/ |
|
17 |
</Location> |
|
18 |
|
|
19 |
<Location /linuxfr/> |
|
20 |
ProxyPass http://linuxfr.org/ |
|
21 |
ProxyPassReverse http://linuxfr.org/ |
|
22 |
ProxyHTMLURLMap / /linuxfr/ |
|
23 |
ProxyHTMLURLMap http://linuxfr.org/ /linuxfr/ |
|
24 |
</Location> |
|
25 |
|
|
26 |
<Location /libe/> |
|
27 |
ProxyPass http://www.liberation.fr/ |
|
28 |
# ProxyPassReverse / |
|
29 |
ProxyHTMLURLMap / /libe/ |
|
30 |
ProxyHTMLURLMap http://www.liberation.fr/ /libe/ |
|
31 |
</Location> |
|
32 |
|
|
33 |
# <Location *> |
|
34 |
# ProxyPass ! |
|
35 |
# </Location> |
|
36 |
|
|
37 |
</VirtualHost> |
Formats disponibles : Unified diff
example vhost configurations + admin web interface which writes vhost files
git-svn-id: svn+ssh://labs.libre-entreprise.org/svnroot/larpe@1 3ed937ae-f919-0410-9a43-8e6f19e4ba6e