comparison env/lib/python3.9/site-packages/docutils/writers/s5_html/__init__.py @ 0:4f3585e2f14b draft default tip

"planemo upload commit 60cee0fc7c0cda8592644e1aad72851dec82c959"
author shellac
date Mon, 22 Mar 2021 18:12:50 +0000
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:4f3585e2f14b
1 # $Id: __init__.py 8412 2019-11-06 18:15:21Z milde $
2 # Authors: Chris Liechti <cliechti@gmx.net>;
3 # David Goodger <goodger@python.org>
4 # Copyright: This module has been placed in the public domain.
5
6 """
7 S5/HTML Slideshow Writer.
8 """
9
10 __docformat__ = 'reStructuredText'
11
12
13 import sys
14 import os
15 import re
16 import docutils
17 from docutils import frontend, nodes, utils
18 from docutils.writers import html4css1
19 from docutils.parsers.rst import directives
20
21 themes_dir_path = utils.relative_path(
22 os.path.join(os.getcwd(), 'dummy'),
23 os.path.join(os.path.dirname(__file__), 'themes'))
24
25 def find_theme(name):
26 # Where else to look for a theme?
27 # Check working dir? Destination dir? Config dir? Plugins dir?
28 path = os.path.join(themes_dir_path, name)
29 if not os.path.isdir(path):
30 raise docutils.ApplicationError(
31 'Theme directory not found: %r (path: %r)' % (name, path))
32 return path
33
34
35 class Writer(html4css1.Writer):
36
37 settings_spec = html4css1.Writer.settings_spec + (
38 'S5 Slideshow Specific Options',
39 'For the S5/HTML writer, the --no-toc-backlinks option '
40 '(defined in General Docutils Options above) is the default, '
41 'and should not be changed.',
42 (('Specify an installed S5 theme by name. Overrides --theme-url. '
43 'The default theme name is "default". The theme files will be '
44 'copied into a "ui/<theme>" directory, in the same directory as the '
45 'destination file (output HTML). Note that existing theme files '
46 'will not be overwritten (unless --overwrite-theme-files is used).',
47 ['--theme'],
48 {'default': 'default', 'metavar': '<name>',
49 'overrides': 'theme_url'}),
50 ('Specify an S5 theme URL. The destination file (output HTML) will '
51 'link to this theme; nothing will be copied. Overrides --theme.',
52 ['--theme-url'],
53 {'metavar': '<URL>', 'overrides': 'theme'}),
54 ('Allow existing theme files in the ``ui/<theme>`` directory to be '
55 'overwritten. The default is not to overwrite theme files.',
56 ['--overwrite-theme-files'],
57 {'action': 'store_true', 'validator': frontend.validate_boolean}),
58 ('Keep existing theme files in the ``ui/<theme>`` directory; do not '
59 'overwrite any. This is the default.',
60 ['--keep-theme-files'],
61 {'dest': 'overwrite_theme_files', 'action': 'store_false'}),
62 ('Set the initial view mode to "slideshow" [default] or "outline".',
63 ['--view-mode'],
64 {'choices': ['slideshow', 'outline'], 'default': 'slideshow',
65 'metavar': '<mode>'}),
66 ('Normally hide the presentation controls in slideshow mode. '
67 'This is the default.',
68 ['--hidden-controls'],
69 {'action': 'store_true', 'default': True,
70 'validator': frontend.validate_boolean}),
71 ('Always show the presentation controls in slideshow mode. '
72 'The default is to hide the controls.',
73 ['--visible-controls'],
74 {'dest': 'hidden_controls', 'action': 'store_false'}),
75 ('Enable the current slide indicator ("1 / 15"). '
76 'The default is to disable it.',
77 ['--current-slide'],
78 {'action': 'store_true', 'validator': frontend.validate_boolean}),
79 ('Disable the current slide indicator. This is the default.',
80 ['--no-current-slide'],
81 {'dest': 'current_slide', 'action': 'store_false'}),))
82
83 settings_default_overrides = {'toc_backlinks': 0}
84
85 config_section = 's5_html writer'
86 config_section_dependencies = ('writers', 'html writers',
87 'html4css1 writer')
88
89 def __init__(self):
90 html4css1.Writer.__init__(self)
91 self.translator_class = S5HTMLTranslator
92
93
94 class S5HTMLTranslator(html4css1.HTMLTranslator):
95
96 s5_stylesheet_template = """\
97 <!-- configuration parameters -->
98 <meta name="defaultView" content="%(view_mode)s" />
99 <meta name="controlVis" content="%(control_visibility)s" />
100 <!-- style sheet links -->
101 <script src="%(path)s/slides.js" type="text/javascript"></script>
102 <link rel="stylesheet" href="%(path)s/slides.css"
103 type="text/css" media="projection" id="slideProj" />
104 <link rel="stylesheet" href="%(path)s/outline.css"
105 type="text/css" media="screen" id="outlineStyle" />
106 <link rel="stylesheet" href="%(path)s/print.css"
107 type="text/css" media="print" id="slidePrint" />
108 <link rel="stylesheet" href="%(path)s/opera.css"
109 type="text/css" media="projection" id="operaFix" />\n"""
110 # The script element must go in front of the link elements to
111 # avoid a flash of unstyled content (FOUC), reproducible with
112 # Firefox.
113
114 disable_current_slide = """
115 <style type="text/css">
116 #currentSlide {display: none;}
117 </style>\n"""
118
119 layout_template = """\
120 <div class="layout">
121 <div id="controls"></div>
122 <div id="currentSlide"></div>
123 <div id="header">
124 %(header)s
125 </div>
126 <div id="footer">
127 %(title)s%(footer)s
128 </div>
129 </div>\n"""
130 # <div class="topleft"></div>
131 # <div class="topright"></div>
132 # <div class="bottomleft"></div>
133 # <div class="bottomright"></div>
134
135 default_theme = 'default'
136 """Name of the default theme."""
137
138 base_theme_file = '__base__'
139 """Name of the file containing the name of the base theme."""
140
141 direct_theme_files = (
142 'slides.css', 'outline.css', 'print.css', 'opera.css', 'slides.js')
143 """Names of theme files directly linked to in the output HTML"""
144
145 indirect_theme_files = (
146 's5-core.css', 'framing.css', 'pretty.css', 'blank.gif', 'iepngfix.htc')
147 """Names of files used indirectly; imported or used by files in
148 `direct_theme_files`."""
149
150 required_theme_files = indirect_theme_files + direct_theme_files
151 """Names of mandatory theme files."""
152
153 def __init__(self, *args):
154 html4css1.HTMLTranslator.__init__(self, *args)
155 #insert S5-specific stylesheet and script stuff:
156 self.theme_file_path = None
157 self.setup_theme()
158 view_mode = self.document.settings.view_mode
159 control_visibility = ('visible', 'hidden')[self.document.settings
160 .hidden_controls]
161 self.stylesheet.append(self.s5_stylesheet_template
162 % {'path': self.theme_file_path,
163 'view_mode': view_mode,
164 'control_visibility': control_visibility})
165 if not self.document.settings.current_slide:
166 self.stylesheet.append(self.disable_current_slide)
167 self.add_meta('<meta name="version" content="S5 1.1" />\n')
168 self.s5_footer = []
169 self.s5_header = []
170 self.section_count = 0
171 self.theme_files_copied = None
172
173 def setup_theme(self):
174 if self.document.settings.theme:
175 self.copy_theme()
176 elif self.document.settings.theme_url:
177 self.theme_file_path = self.document.settings.theme_url
178 else:
179 raise docutils.ApplicationError(
180 'No theme specified for S5/HTML writer.')
181
182 def copy_theme(self):
183 """
184 Locate & copy theme files.
185
186 A theme may be explicitly based on another theme via a '__base__'
187 file. The default base theme is 'default'. Files are accumulated
188 from the specified theme, any base themes, and 'default'.
189 """
190 settings = self.document.settings
191 path = find_theme(settings.theme)
192 theme_paths = [path]
193 self.theme_files_copied = {}
194 required_files_copied = {}
195 # This is a link (URL) in HTML, so we use "/", not os.sep:
196 self.theme_file_path = '%s/%s' % ('ui', settings.theme)
197 if settings._destination:
198 dest = os.path.join(
199 os.path.dirname(settings._destination), 'ui', settings.theme)
200 if not os.path.isdir(dest):
201 os.makedirs(dest)
202 else:
203 # no destination, so we can't copy the theme
204 return
205 default = False
206 while path:
207 for f in os.listdir(path): # copy all files from each theme
208 if f == self.base_theme_file:
209 continue # ... except the "__base__" file
210 if ( self.copy_file(f, path, dest)
211 and f in self.required_theme_files):
212 required_files_copied[f] = 1
213 if default:
214 break # "default" theme has no base theme
215 # Find the "__base__" file in theme directory:
216 base_theme_file = os.path.join(path, self.base_theme_file)
217 # If it exists, read it and record the theme path:
218 if os.path.isfile(base_theme_file):
219 with open(base_theme_file) as f:
220 lines = f.readlines()
221 for line in lines:
222 line = line.strip()
223 if line and not line.startswith('#'):
224 path = find_theme(line)
225 if path in theme_paths: # check for duplicates (cycles)
226 path = None # if found, use default base
227 else:
228 theme_paths.append(path)
229 break
230 else: # no theme name found
231 path = None # use default base
232 else: # no base theme file found
233 path = None # use default base
234 if not path:
235 path = find_theme(self.default_theme)
236 theme_paths.append(path)
237 default = True
238 if len(required_files_copied) != len(self.required_theme_files):
239 # Some required files weren't found & couldn't be copied.
240 required = list(self.required_theme_files)
241 for f in required_files_copied.keys():
242 required.remove(f)
243 raise docutils.ApplicationError(
244 'Theme files not found: %s'
245 % ', '.join(['%r' % f for f in required]))
246
247 files_to_skip_pattern = re.compile(r'~$|\.bak$|#$|\.cvsignore$')
248
249 def copy_file(self, name, source_dir, dest_dir):
250 """
251 Copy file `name` from `source_dir` to `dest_dir`.
252 Return 1 if the file exists in either `source_dir` or `dest_dir`.
253 """
254 source = os.path.join(source_dir, name)
255 dest = os.path.join(dest_dir, name)
256 if dest in self.theme_files_copied:
257 return 1
258 else:
259 self.theme_files_copied[dest] = 1
260 if os.path.isfile(source):
261 if self.files_to_skip_pattern.search(source):
262 return None
263 settings = self.document.settings
264 if os.path.exists(dest) and not settings.overwrite_theme_files:
265 settings.record_dependencies.add(dest)
266 else:
267 src_file = open(source, 'rb')
268 src_data = src_file.read()
269 src_file.close()
270 dest_file = open(dest, 'wb')
271 dest_dir = dest_dir.replace(os.sep, '/')
272 dest_file.write(src_data.replace(b'ui/default',
273 dest_dir[dest_dir.rfind('ui/'):].encode(
274 sys.getfilesystemencoding())))
275 dest_file.close()
276 settings.record_dependencies.add(source)
277 return 1
278 if os.path.isfile(dest):
279 return 1
280
281 def depart_document(self, node):
282 self.head_prefix.extend([self.doctype,
283 self.head_prefix_template %
284 {'lang': self.settings.language_code}])
285 self.html_prolog.append(self.doctype)
286 self.meta.insert(0, self.content_type % self.settings.output_encoding)
287 self.head.insert(0, self.content_type % self.settings.output_encoding)
288 if self.math_header:
289 if self.math_output == 'mathjax':
290 self.head.extend(self.math_header)
291 else:
292 self.stylesheet.extend(self.math_header)
293 # skip content-type meta tag with interpolated charset value:
294 self.html_head.extend(self.head[1:])
295 self.fragment.extend(self.body)
296 # special S5 code up to the next comment line
297 header = ''.join(self.s5_header)
298 footer = ''.join(self.s5_footer)
299 title = ''.join(self.html_title).replace('<h1 class="title">', '<h1>')
300 layout = self.layout_template % {'header': header,
301 'title': title,
302 'footer': footer}
303 self.body_prefix.extend(layout)
304 self.body_prefix.append('<div class="presentation">\n')
305 self.body_prefix.append(
306 self.starttag({'classes': ['slide'], 'ids': ['slide0']}, 'div'))
307 if not self.section_count:
308 self.body.append('</div>\n')
309 #
310 self.body_suffix.insert(0, '</div>\n')
311 self.html_body.extend(self.body_prefix[1:] + self.body_pre_docinfo
312 + self.docinfo + self.body
313 + self.body_suffix[:-1])
314
315 def depart_footer(self, node):
316 start = self.context.pop()
317 self.s5_footer.append('<h2>')
318 self.s5_footer.extend(self.body[start:])
319 self.s5_footer.append('</h2>')
320 del self.body[start:]
321
322 def depart_header(self, node):
323 start = self.context.pop()
324 header = ['<div id="header">\n']
325 header.extend(self.body[start:])
326 header.append('\n</div>\n')
327 del self.body[start:]
328 self.s5_header.extend(header)
329
330 def visit_section(self, node):
331 if not self.section_count:
332 self.body.append('\n</div>\n')
333 self.section_count += 1
334 self.section_level += 1
335 if self.section_level > 1:
336 # dummy for matching div's
337 self.body.append(self.starttag(node, 'div', CLASS='section'))
338 else:
339 self.body.append(self.starttag(node, 'div', CLASS='slide'))
340
341 def visit_subtitle(self, node):
342 if isinstance(node.parent, nodes.section):
343 level = self.section_level + self.initial_header_level - 1
344 if level == 1:
345 level = 2
346 tag = 'h%s' % level
347 self.body.append(self.starttag(node, tag, ''))
348 self.context.append('</%s>\n' % tag)
349 else:
350 html4css1.HTMLTranslator.visit_subtitle(self, node)
351
352 def visit_title(self, node):
353 html4css1.HTMLTranslator.visit_title(self, node)