1 # Part of Objavi2, which turns html manuals into books
3 # Copyright (C) 2009 Douglas Bagnall
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License along
16 # with this program; if not, write to the Free Software Foundation, Inc.,
17 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22 from getopt
import gnu_getopt
24 from objavi
.book_utils
import log
25 from objavi
import config
27 SERVER_NAME
= os
.environ
.get('SERVER_NAME', 'localhost')
29 def parse_args(arg_validators
):
30 """Read and validate CGI or commandline arguments, putting the
31 good ones into the returned dictionary. Command line arguments
32 should be in the form --title='A Book'.
34 query
= cgi
.FieldStorage()
35 options
, args
= gnu_getopt(sys
.argv
[1:], '', [x
+ '=' for x
in arg_validators
])
36 options
= dict(options
)
37 log(query
, debug
='STARTUP')
39 for key
, validator
in arg_validators
.items():
40 value
= query
.getfirst(key
, options
.get('--' + key
, None))
41 log('%s: %s' % (key
, value
), debug
='STARTUP')
43 if validator
is not None and not validator(value
):
44 log("argument '%s' is not valid ('%s')" % (key
, value
))
48 log(data
, debug
='STARTUP')
51 def super_bleach(dirty_name
):
52 """Replace potentially nasty characters with safe ones."""
53 # a bit drastic: refine if it matters
54 name
= ''.join((x
if x
.isalnum() else '-') for x
in dirty_name
)
60 ## common between different versions of objavi
62 def get_server_list():
63 return sorted(k
for k
, v
in config
.SERVER_DEFAULTS
.items() if v
['display'])
66 #order by increasing areal size.
67 def calc_size(name
, pointsize
, klass
):
69 mmx
= pointsize
[0] * config
.POINT_2_MM
70 mmy
= pointsize
[1] * config
.POINT_2_MM
71 return (mmx
* mmy
, name
, klass
,
72 '%s (%dmm x %dmm)' % (name
, mmx
, mmy
))
74 return (0, name
, klass
, name
) # presumably 'custom'
76 return [x
[1:] for x
in sorted(calc_size(k
, v
.get('pointsize'), v
.get('class', ''))
77 for k
, v
in config
.PAGE_SIZE_DATA
.iteritems())
81 """convert htdocs-relative addresses to local file paths"""
82 return config
.HTDOCS
+ '/' + url
.lstrip('/')
84 _htdocs
= os
.path
.abspath(config
.HTDOCS
)
85 def path2url(path
, default
='/missing_path?%(path)s', full
=False):
86 """convert local file paths to htdocs-relative addresses. If the
87 file is not in the web tree, return default"""
88 if path
.startswith('file:///'):
90 path
= os
.path
.abspath(path
)
91 if path
.startswith(_htdocs
):
92 path
= path
[len(_htdocs
):]
94 path
= default
% {'path': urllib
.quote(path
)}
96 return 'http://%s%s' % (SERVER_NAME
, path
)
100 def get_default_css(server
=config
.DEFAULT_SERVER
, mode
='book'):
101 """Get the default CSS text for the selected server"""
103 cssfile
= url2path(config
.SERVER_DEFAULTS
[server
]['css-%s' % mode
])
111 """Links to various example pdfs."""
113 for script
in os
.listdir(config
.FONT_EXAMPLE_SCRIPT_DIR
):
114 if not script
.isalnum():
115 log("warning: font-sample %s won't work; skipping" % script
)
117 links
.append('<a href="%s?script=%s">%s</a>' % (config
.FONT_LIST_URL
, script
, script
))
120 ## Helper functions for parse_args
123 #spaces?, digits!, dot?, digits?, spaces?
124 #return re.compile(r'^\s*[+-]?\d+\.?\d*\s*$').match
131 def isfloat_or_auto(s
):
132 return isfloat(s
) or s
.lower() in ('', 'auto')
135 # 10 or 13 digits with any number of hyphens, perhaps with check-digit missing
136 s
= s
.replace('-', '')
137 return (re
.match(r
'^\d+[\dXx*]$', s
) and len(s
) in (9, 10, 12, 13))
140 """Check whether the string approximates a valid http URL."""
144 return re
.match(r
'^https?://'
145 r
'(?:(?:[a-z0-9]+(?:-*[a-z0-9]+)*\.)+[a-z]{2,8}|'
146 r
'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})'
148 r
'(?:/?|/\S+)$', s
, re
.I
152 return re
.match(r
'^[\w-]+$', s
)
158 except UnicodeDecodeError:
163 ## Formatting of lists
165 def optionise(items
, default
=None):
166 """Make a list of strings into an html option string, as would fit
167 inside <select> tags."""
170 if isinstance(x
, str):
173 # couple: value, name
175 options
.append('<option selected="selected" value="%s">%s</option>' % x
)
177 options
.append('<option value="%s">%s</option>' % x
)
179 # triple: value, class, name
181 options
.append('<option selected="selected" value="%s" class="%s">%s</option>' % x
)
183 options
.append('<option value="%s" class="%s">%s</option>' % x
)
185 return '\n'.join(options
)
189 """Make a list of strings into html <li> items, to fit in a <ul>
191 return '\n'.join('<li>%s</li>' % x
for x
in items
)
195 def output_blob_and_exit(blob
, content_type
="application/octet-stream", filename
=None):
196 print 'Content-type: %s\nContent-length: %s' % (content_type
, len(blob
))
197 if filename
is not None:
198 print 'Content-Disposition: attachment; filename="%s"' % filename
203 def output_blob_and_shut_up(blob
, content_type
="application/octet-stream", filename
=None):
204 print 'Content-type: %s\nContent-length: %s' % (content_type
, len(blob
))
205 if filename
is not None:
206 print 'Content-Disposition: attachment; filename="%s"' % filename
210 devnull
= open('/dev/null', 'w')
211 os
.dup2(devnull
.fileno(), sys
.stdout
.fileno())
214 ##Decorator function for output
215 def output_and_exit(f
, content_type
="text/html; charset=utf-8"):
216 """Decorator: prefix function output with http headers and exit
217 immediately after."""
218 def output(*args
, **kwargs
):
219 content
= f(*args
, **kwargs
)
220 print "Content-type: %s" % content_type
221 print "Content-length: %s" % len(content
)
228 def print_template_and_exit(template
, mapping
):
232 return string
% mapping
234 def try_to_kill(pid
, signal
=15):
235 log('kill -%s %s ' % (signal
, pid
))
237 os
.kill(int(pid
), signal
)
239 log('PID %s seems dead (kill -%s gives %s)' % (pid
, signal
, e
))