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."""
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
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).
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',
52 "rotate": u
"rotate".__eq
__,
55 __doc__
+= '\nValid arguments are: %s.\n' % ', '.join(ARG_VALIDATORS
.keys())
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'.
62 query
= cgi
.FieldStorage()
63 options
, args
= gnu_getopt(sys
.argv
[1:], '', [x
+ '=' for x
in ARG_VALIDATORS
])
64 options
= dict(options
)
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')
71 if validator
is not None and not validator(value
):
72 log("argument '%s' is not valid ('%s')" % (key
, value
))
76 log(data
, debug
='STARTUP')
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.
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()):
99 url
= 'http://%s/bin/view/TWiki/WebLeftBarWebsList?skin=text' % server
105 items
= sorted(re
.findall(r
'/bin/view/([\w/]+)/WebHome', s
))
107 f
= open(cache_name
, 'w')
108 f
.write('\n'.join(items
))
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]))
123 def optionise(items
, default
=None):
126 if isinstance(x
, str):
128 options
.append('<option selected="selected">%s</option>' % x
)
130 options
.append('<option>%s</option>' % x
)
133 # couple: value, name
135 options
.append('<option selected="selected" value="%s">%s</option>' % x
)
137 options
.append('<option value="%s">%s</option>' % x
)
139 return '\n'.join(options
)
141 def get_default_css(server
=DEFAULT_SERVER
):
143 cssfile
= SERVER_DEFAULTS
[server
]['css']
152 """Links to various example pdfs."""
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
)
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
)
166 f
= open(config
.FONT_LIST_INCLUDE
)
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
,
181 def make_progress_page(webname
, bookname
):
182 f
= open(PROGRESS_TEMPLATE
)
187 'bookname': bookname
,
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>'
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__':
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'
219 print "Content-type: text/html; charset=utf-8\n"
221 if mode
== 'booklist':
222 print optionise(get_book_list(server
), default
=webname
)
225 #XX sending as text/html, but it doesn't really matter
226 print get_default_css(server
=server
)
229 if not webname
or not server
:
231 show_form(args
, server
, webname
, size
)
236 # so we're making a book.
237 bookname
= make_book_name(webname
, server
)
239 progress_bar
= make_progress_page(webname
, bookname
)
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
,
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()
271 book
.notify_watcher('finished')