PageSettings object doesn't need so much irrelevant information
[objavi2.git] / objavi2.py
blob4c479f9c84c81669a38d851c2a9a0fd78b5a0b22
1 #!/usr/bin/python
3 # Part of Objavi2, which turns html manuals into books
5 # Copyright (C) 2009 Douglas Bagnall
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 2 of the License, or
10 # (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License along
18 # with this program; if not, write to the Free Software Foundation, Inc.,
19 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 """Make a pdf from the specified book."""
22 import os, sys
23 import cgi
24 import re, time
25 from urllib2 import urlopen
26 from getopt import gnu_getopt
28 from fmbook import log, Book
29 from fmbook import PAGE_SETTINGS, SERVER_DEFAULTS, DEFAULT_SERVER
31 import config
32 from config import BOOK_LIST_CACHE, BOOK_LIST_CACHE_DIR
34 FORM_TEMPLATE = os.path.abspath('templates/form.html')
35 PROGRESS_TEMPLATE = os.path.abspath('templates/progress.html')
37 # ARG_VALIDATORS is a mapping between the expected cgi arguments and
38 # functions to validate their values. (None means no validation).
40 ARG_VALIDATORS = {
41 "webName": re.compile(r'^(\w+/?)*\w+$').match, # can be: BlahBlah/Blah_Blah
42 "css": None, # an url, empty (for default), or css content
43 "title": lambda x: len(x) < 999,
44 "header": None, # header text, UNUSED
45 "isbn": lambda x: x.isdigit() and len(x) == 13,
46 "license": lambda x: len(x) < 999, #should be a codename?
47 "server": SERVER_DEFAULTS.__contains__,
48 "engine": config.ENGINES.__contains__,
49 "booksize": PAGE_SETTINGS.__contains__,
50 "cgi-context": lambda x: x.lower() in '1true0false',
51 "mode": str.isalnum,
52 "rotate": u"rotate".__eq__,
55 __doc__ += '\nValid arguments are: %s.\n' % ', '.join(ARG_VALIDATORS.keys())
57 def parse_args():
58 """Read and validate CGI or commandline arguments, putting the
59 good ones into the returned dictionary. Command line arguments
60 should be in the form --title='A Book'.
61 """
62 query = cgi.FieldStorage()
63 options, args = gnu_getopt(sys.argv[1:], '', [x + '=' for x in ARG_VALIDATORS])
64 options = dict(options)
65 log(options)
66 data = {}
67 for key, validator in ARG_VALIDATORS.items():
68 value = query.getfirst(key, options.get('--' + key, None))
69 log('%s: %s' % (key, value), debug='STARTUP')
70 if value is not None:
71 if validator is not None and not validator(value):
72 log("argument '%s' is not valid ('%s')" % (key, value))
73 continue
74 data[key] = value
76 log(data, debug='STARTUP')
77 return data
79 def get_server_list():
80 return sorted(SERVER_DEFAULTS.keys())
83 def get_book_list(server):
84 """Ask the server for a list of books. Floss Manual TWikis keep such a list at
85 /bin/view/TWiki/WebLeftBarWebsList?skin=text but it needs a bit of processing
87 If BOOK_LIST_CACHE is non-zero, the book list won't be re-fetched
88 in that many seconds, rather it will be read from disk.
89 """
90 if BOOK_LIST_CACHE:
91 cache_name = os.path.join(BOOK_LIST_CACHE_DIR, '%s.booklist' % server)
92 if (os.path.exists(cache_name) and
93 os.stat(cache_name).st_mtime + BOOK_LIST_CACHE > time.time()):
94 f = open(cache_name)
95 s = f.read()
96 f.close()
97 return s.split()
99 url = 'http://%s/bin/view/TWiki/WebLeftBarWebsList?skin=text' % server
100 #XXX should use lxml
101 log(url)
102 f = urlopen(url)
103 s = f.read()
104 f.close()
105 items = sorted(re.findall(r'/bin/view/([\w/]+)/WebHome', s))
106 if BOOK_LIST_CACHE:
107 f = open(cache_name, 'w')
108 f.write('\n'.join(items))
109 f.close()
110 return items
112 def get_size_list():
113 #XXX PAGE_SETTINGS instances are only constructed for this list.
114 # the area and mmsizes could be calculated seperately here from PAGE_SIZE_DATA
116 #order by increasing areal size.
117 ordered = [x[1] for x in
118 sorted((v.area, v) for v in PAGE_SETTINGS.values())]
119 return [(v.name, '%s (%dmm x %dmm)' % (v.name, v.mmsize[0], v.mmsize[1]))
120 for v in ordered]
123 def optionise(items, default=None):
124 options = []
125 for x in items:
126 if isinstance(x, str):
127 if x == default:
128 options.append('<option selected="selected">%s</option>' % x)
129 else:
130 options.append('<option>%s</option>' % x)
131 else:
132 log(x, x[0])
133 # couple: value, name
134 if x[0] == default:
135 options.append('<option selected="selected" value="%s">%s</option>' % x)
136 else:
137 options.append('<option value="%s">%s</option>' % x)
139 return '\n'.join(options)
141 def get_default_css(server=DEFAULT_SERVER):
142 log(server)
143 cssfile = SERVER_DEFAULTS[server]['css']
144 log(cssfile)
145 f = open(cssfile)
146 s = f.read()
147 f.close()
148 #log(s)
149 return s
151 def font_links():
152 """Links to various example pdfs."""
153 links = []
154 for script in os.listdir(config.FONT_EXAMPLE_SCRIPT_DIR):
155 if not script.isalnum():
156 log("warning: font-sample %s won't work; skipping" % script)
157 continue
158 links.append('<a href="%s?script=%s">%s</a>' % (config.FONT_LIST_URL, script, script))
159 return ', '.join(links)
162 def show_form(args, server, webname, size='COMICBOOK', engine='webkit'):
163 f = open(FORM_TEMPLATE)
164 template = f.read()
165 f.close()
166 f = open(config.FONT_LIST_INCLUDE)
167 font_list = f.read()
168 f.close()
169 d = {
170 'server_options': optionise(get_server_list(), default=server),
171 'book_options': optionise(get_book_list(server), default=webname),
172 'size_options': optionise(get_size_list(), default=size),
173 'engines': optionise(config.ENGINES.keys(), default=engine),
174 'css': get_default_css(server),
175 'font_links': font_links(),
176 'font_list': font_list,
178 print template % d
181 def make_progress_page(webname, bookname):
182 f = open(PROGRESS_TEMPLATE)
183 template = f.read()
184 f.close()
185 d = {
186 'webname': webname,
187 'bookname': bookname,
189 print template % d
190 def progress_notifier(message):
191 print ('<script type="text/javascript">\n'
192 'objavi_show_progress("%s");\n'
193 '</script>' % message
195 if message == 'finished':
196 print '</body></html>'
197 sys.stdout.flush()
198 return progress_notifier
200 def print_progress(message):
201 print '******* got message "%s"' %message
203 def make_book_name(webname, server):
204 lang = SERVER_DEFAULTS.get(server, SERVER_DEFAULTS[DEFAULT_SERVER])['lang']
205 webname = ''.join(x for x in webname if x.isalnum())
206 return '%s-%s-%s.pdf' % (webname, lang,
207 time.strftime('%Y.%m.%d-%H.%M.%S'))
209 if __name__ == '__main__':
210 args = parse_args()
211 webname = args.get('webName')
212 server = args.get('server', config.DEFAULT_SERVER)
213 size = args.get('booksize', config.DEFAULT_SIZE)
214 engine = args.get('engine', config.DEFAULT_ENGINE)
215 mode = args.get('mode')
217 cgi_context = 'SERVER_NAME' in os.environ or args.get('cgi-context', 'NO').lower() in '1true'
218 if cgi_context:
219 print "Content-type: text/html; charset=utf-8\n"
221 if mode == 'booklist':
222 print optionise(get_book_list(server), default=webname)
223 sys.exit()
224 if mode == 'css':
225 #XX sending as text/html, but it doesn't really matter
226 print get_default_css(server=server)
227 sys.exit()
229 if not webname or not server:
230 if cgi_context:
231 show_form(args, server, webname, size)
232 else:
233 print __doc__
234 sys.exit()
236 # so we're making a book.
237 bookname = make_book_name(webname, server)
238 if cgi_context:
239 progress_bar = make_progress_page(webname, bookname)
240 else:
241 progress_bar = print_progress
243 #XXX could use 'with Book() as book': to makesure cleanup happens
244 # (or try ... finally)
246 book = Book(webname, server, bookname, pagesize=size, engine=engine,
247 watcher=progress_bar
250 if cgi_context:
251 book.spawn_x()
253 book.load()
255 book.set_title(args.get('title'))
256 book.add_css(args.get('css'))
258 book.compose_inside_cover(args.get('license'), args.get('isbn'))
260 book.add_section_titles()
262 book.make_pdf()
264 if "rotate" in args:
265 book.rotate180()
267 book.publish_pdf()
269 book.cleanup()
271 book.notify_watcher('finished')