starting with sphinx 7.2.0, root is a pathlib.Path not a string anymore
[git-cola.git] / extras / sphinxtogithub / sphinxtogithub.py
blob10a0b8ef45c9618504b75d3aabea7235cdc4245b
1 #! /usr/bin/env python
3 from __future__ import absolute_import
5 from optparse import OptionParser
7 import os
8 import sys
9 import shutil
10 import codecs
13 def stdout(msg):
14 sys.stdout.write(msg + '\n')
17 class DirHelper(object):
18 def __init__(self, is_dir, list_dir, walk, rmtree):
19 self.is_dir = is_dir
20 self.list_dir = list_dir
21 self.walk = walk
22 self.rmtree = rmtree
25 class FileSystemHelper(object):
26 def __init__(self, open_, path_join, move, exists):
27 self.open_ = open_
28 self.path_join = path_join
29 self.move = move
30 self.exists = exists
33 class Replacer(object):
34 "Encapsulates a simple text replace"
36 def __init__(self, from_, to):
37 self.from_ = from_
38 self.to = to
40 def process(self, text):
41 return text.replace(self.from_, self.to)
44 class FileHandler(object):
45 "Applies a series of replacements the contents of a file inplace"
47 def __init__(self, name, replacers, opener):
48 self.name = name
49 self.replacers = replacers
50 self.opener = opener
52 def process(self):
53 text = self.opener(self.name, "r").read()
55 for replacer in self.replacers:
56 text = replacer.process(text)
58 self.opener(self.name, "w").write(text)
61 class Remover(object):
62 def __init__(self, exists, remove):
63 self.exists = exists
64 self.remove = remove
66 def __call__(self, name):
67 if self.exists(name):
68 self.remove(name)
71 class ForceRename(object):
72 def __init__(self, renamer, remove):
73 self.renamer = renamer
74 self.remove = remove
76 def __call__(self, from_, to):
77 self.remove(to)
78 self.renamer(from_, to)
81 class VerboseRename(object):
82 def __init__(self, renamer, stream):
83 self.renamer = renamer
84 self.stream = stream
86 def __call__(self, from_, to):
87 self.stream.write(
88 "Renaming directory '%s' -> '%s'\n"
89 % (os.path.basename(from_), os.path.basename(to))
92 self.renamer(from_, to)
95 class DirectoryHandler(object):
96 "Encapsulates renaming a directory by removing its first character"
98 def __init__(self, name, root, renamer):
99 self.name = name
100 self.new_name = name[1:]
101 self.root = str(root)
102 self.renamer = renamer
104 def path(self):
105 return os.path.join(self.root, self.name)
107 def relative_path(self, directory, filename):
108 path = directory.replace(self.root, "", 1)
109 return os.path.join(path, filename)
111 def new_relative_path(self, directory, filename):
112 path = self.relative_path(directory, filename)
113 return path.replace(self.name, self.new_name, 1)
115 def process(self):
116 from_ = os.path.join(self.root, self.name)
117 to = os.path.join(self.root, self.new_name)
118 self.renamer(from_, to)
121 class HandlerFactory(object):
122 def create_file_handler(self, name, replacers, opener):
123 return FileHandler(name, replacers, opener)
125 def create_dir_handler(self, name, root, renamer):
126 return DirectoryHandler(name, root, renamer)
129 class OperationsFactory(object):
130 def create_force_rename(self, renamer, remover):
131 return ForceRename(renamer, remover)
133 def create_verbose_rename(self, renamer, stream):
134 return VerboseRename(renamer, stream)
136 def create_replacer(self, from_, to):
137 return Replacer(from_, to)
139 def create_remover(self, exists, remove):
140 return Remover(exists, remove)
143 class Layout(object):
145 Applies a set of operations which result in the layout
146 of a directory changing
149 def __init__(self, directory_handlers, file_handlers):
150 self.directory_handlers = directory_handlers
151 self.file_handlers = file_handlers
153 def process(self):
154 for handler in self.file_handlers:
155 handler.process()
157 for handler in self.directory_handlers:
158 handler.process()
161 class NullLayout(object):
163 Layout class that does nothing when asked to process
166 def process(self):
167 pass
170 class LayoutFactory(object):
171 "Creates a layout object"
173 def __init__(
174 self,
175 operations_factory,
176 handler_factory,
177 file_helper,
178 dir_helper,
179 verbose,
180 stream,
181 force,
183 self.operations_factory = operations_factory
184 self.handler_factory = handler_factory
186 self.file_helper = file_helper
187 self.dir_helper = dir_helper
189 self.verbose = verbose
190 self.output_stream = stream
191 self.force = force
193 def create_layout(self, path):
194 contents = self.dir_helper.list_dir(path)
196 renamer = self.file_helper.move
198 if self.force:
199 remove = self.operations_factory.create_remover(
200 self.file_helper.exists, self.dir_helper.rmtree
202 renamer = self.operations_factory.create_force_rename(renamer, remove)
204 if self.verbose:
205 renamer = self.operations_factory.create_verbose_rename(
206 renamer, self.output_stream
209 # Build list of directories to process
210 directories = [d for d in contents if self.is_underscore_dir(path, d)]
211 underscore_directories = [
212 self.handler_factory.create_dir_handler(d, path, renamer)
213 for d in directories
216 if not underscore_directories:
217 if self.verbose:
218 self.output_stream.write(
219 "No top level directories starting with an underscore "
220 "were found in '%s'\n" % path
222 return NullLayout()
224 # Build list of files that are in those directories
225 replacers = []
226 for handler in underscore_directories:
227 for directory, _, files in self.dir_helper.walk(handler.path()):
228 for f in files:
229 replacers.append(
230 self.operations_factory.create_replacer(
231 handler.relative_path(directory, f),
232 handler.new_relative_path(directory, f),
236 # Build list of handlers to process all files
237 filelist = []
238 for root, _, files in self.dir_helper.walk(path):
239 for f in files:
240 if f.endswith(".html"):
241 filelist.append(
242 self.handler_factory.create_file_handler(
243 self.file_helper.path_join(root, f),
244 replacers,
245 self.file_helper.open_,
248 if f.endswith(".js"):
249 filelist.append(
250 self.handler_factory.create_file_handler(
251 self.file_helper.path_join(root, f),
253 self.operations_factory.create_replacer(
254 "'_sources/'", "'sources/'"
257 self.file_helper.open_,
261 return Layout(underscore_directories, filelist)
263 def is_underscore_dir(self, path, directory):
264 return self.dir_helper.is_dir(
265 self.file_helper.path_join(path, directory)
266 ) and directory.startswith("_")
269 def sphinx_extension(app, exception):
270 "Wrapped up as a Sphinx Extension"
272 if app.builder.name not in ("html", "dirhtml"):
273 return
275 if not app.config.sphinx_to_github:
276 if app.config.sphinx_to_github_verbose:
277 stdout("Sphinx-to-github: Disabled, doing nothing.")
278 return
280 if exception:
281 if app.config.sphinx_to_github_verbose:
282 msg = "Sphinx-to-github: " "Exception raised in main build, doing nothing."
283 stdout(msg)
284 return
286 dir_helper = DirHelper(os.path.isdir, os.listdir, os.walk, shutil.rmtree)
288 file_helper = FileSystemHelper(
289 lambda f, mode: codecs.open(f, mode, app.config.sphinx_to_github_encoding),
290 os.path.join,
291 shutil.move,
292 os.path.exists,
295 operations_factory = OperationsFactory()
296 handler_factory = HandlerFactory()
298 layout_factory = LayoutFactory(
299 operations_factory,
300 handler_factory,
301 file_helper,
302 dir_helper,
303 app.config.sphinx_to_github_verbose,
304 sys.stdout,
305 force=True,
308 layout = layout_factory.create_layout(app.outdir)
309 layout.process()
312 def setup(app):
313 "Setup function for Sphinx Extension"
315 app.add_config_value("sphinx_to_github", True, '')
316 app.add_config_value("sphinx_to_github_verbose", True, '')
317 app.add_config_value("sphinx_to_github_encoding", 'utf-8', '')
319 app.connect("build-finished", sphinx_extension)
322 def main(args):
323 usage = "usage: %prog [options] <html directory>"
324 parser = OptionParser(usage=usage)
325 parser.add_option(
326 "-v",
327 "--verbose",
328 action="store_true",
329 dest="verbose",
330 default=False,
331 help="Provides verbose output",
333 parser.add_option(
334 "-e",
335 "--encoding",
336 action="store",
337 dest="encoding",
338 default="utf-8",
339 help="Encoding for reading and writing files",
341 opts, args = parser.parse_args(args)
343 try:
344 path = args[0]
345 except IndexError:
346 sys.stderr.write(
347 "Error - Expecting path to html directory:" "sphinx-to-github <path>\n"
349 return
351 dir_helper = DirHelper(os.path.isdir, os.listdir, os.walk, shutil.rmtree)
353 file_helper = FileSystemHelper(
354 lambda f, mode: codecs.open(f, mode, opts.encoding),
355 os.path.join,
356 shutil.move,
357 os.path.exists,
360 operations_factory = OperationsFactory()
361 handler_factory = HandlerFactory()
363 layout_factory = LayoutFactory(
364 operations_factory,
365 handler_factory,
366 file_helper,
367 dir_helper,
368 opts.verbose,
369 sys.stdout,
370 force=False,
373 layout = layout_factory.create_layout(path)
374 layout.process()
377 if __name__ == "__main__":
378 main(sys.argv[1:])