utils: Use the interruptable() decorator
[git-cola.git] / extras / sphinxtogithub / sphinxtogithub.py
blob0de0411832519643e8a74275519552bc29428078
1 #! /usr/bin/env python
3 from optparse import OptionParser
4 import warnings
5 import os
6 import sys
7 import shutil
10 class DirHelper(object):
12 def __init__(self, is_dir, list_dir, walk, rmtree):
14 self.is_dir = is_dir
15 self.list_dir = list_dir
16 self.walk = walk
17 self.rmtree = rmtree
19 class FileSystemHelper(object):
21 def __init__(self, open_, path_join, move, exists):
23 self.open_ = open_
24 self.path_join = path_join
25 self.move = move
26 self.exists = exists
28 class Replacer(object):
29 "Encapsulates a simple text replace"
31 def __init__(self, from_, to):
33 self.from_ = from_
34 self.to = to
36 def process(self, text):
38 return text.replace( self.from_, self.to )
40 class FileHandler(object):
41 "Applies a series of replacements the contents of a file inplace"
43 def __init__(self, name, replacers, opener):
45 self.name = name
46 self.replacers = replacers
47 self.opener = opener
49 def process(self):
51 text = self.opener(self.name).read()
53 for replacer in self.replacers:
54 text = replacer.process( text )
56 self.opener(self.name, "w").write(text)
58 class Remover(object):
60 def __init__(self, exists, remove):
61 self.exists = exists
62 self.remove = remove
64 def __call__(self, name):
66 if self.exists(name):
67 self.remove(name)
69 class ForceRename(object):
71 def __init__(self, renamer, remove):
73 self.renamer = renamer
74 self.remove = remove
76 def __call__(self, from_, to):
78 self.remove(to)
79 self.renamer(from_, to)
81 class VerboseRename(object):
83 def __init__(self, renamer, stream):
85 self.renamer = renamer
86 self.stream = stream
88 def __call__(self, from_, to):
90 self.stream.write(
91 "Renaming directory '%s' -> '%s'\n"
92 % (os.path.basename(from_), os.path.basename(to))
95 self.renamer(from_, to)
98 class DirectoryHandler(object):
99 "Encapsulates renaming a directory by removing its first character"
101 def __init__(self, name, root, renamer):
103 self.name = name
104 self.new_name = name[1:]
105 self.root = root + os.sep
106 self.renamer = renamer
108 def path(self):
110 return os.path.join(self.root, self.name)
112 def relative_path(self, directory, filename):
114 path = directory.replace(self.root, "", 1)
115 return os.path.join(path, filename)
117 def new_relative_path(self, directory, filename):
119 path = self.relative_path(directory, filename)
120 return path.replace(self.name, self.new_name, 1)
122 def process(self):
124 from_ = os.path.join(self.root, self.name)
125 to = os.path.join(self.root, self.new_name)
126 self.renamer(from_, to)
129 class HandlerFactory(object):
131 def create_file_handler(self, name, replacers, opener):
133 return FileHandler(name, replacers, opener)
135 def create_dir_handler(self, name, root, renamer):
137 return DirectoryHandler(name, root, renamer)
140 class OperationsFactory(object):
142 def create_force_rename(self, renamer, remover):
144 return ForceRename(renamer, remover)
146 def create_verbose_rename(self, renamer, stream):
148 return VerboseRename(renamer, stream)
150 def create_replacer(self, from_, to):
152 return Replacer(from_, to)
154 def create_remover(self, exists, remove):
156 return Remover(exists, remove)
159 class Layout(object):
161 Applies a set of operations which result in the layout
162 of a directory changing
165 def __init__(self, directory_handlers, file_handlers):
167 self.directory_handlers = directory_handlers
168 self.file_handlers = file_handlers
170 def process(self):
172 for handler in self.file_handlers:
173 handler.process()
175 for handler in self.directory_handlers:
176 handler.process()
179 class NullLayout(object):
181 Layout class that does nothing when asked to process
183 def process(self):
184 pass
186 class LayoutFactory(object):
187 "Creates a layout object"
189 def __init__(self, operations_factory, handler_factory, file_helper, dir_helper, verbose, stream, force):
191 self.operations_factory = operations_factory
192 self.handler_factory = handler_factory
194 self.file_helper = file_helper
195 self.dir_helper = dir_helper
197 self.verbose = verbose
198 self.output_stream = stream
199 self.force = force
201 def create_layout(self, path):
203 contents = self.dir_helper.list_dir(path)
205 renamer = self.file_helper.move
207 if self.force:
208 remove = self.operations_factory.create_remover(self.file_helper.exists, self.dir_helper.rmtree)
209 renamer = self.operations_factory.create_force_rename(renamer, remove)
211 if self.verbose:
212 renamer = self.operations_factory.create_verbose_rename(renamer, self.output_stream)
214 # Build list of directories to process
215 directories = [d for d in contents if self.is_underscore_dir(path, d)]
216 underscore_directories = [
217 self.handler_factory.create_dir_handler(d, path, renamer)
218 for d in directories
221 if not underscore_directories:
222 if self.verbose:
223 self.output_stream.write(
224 "No top level directories starting with an underscore "
225 "were found in '%s'\n" % path
227 return NullLayout()
229 # Build list of files that are in those directories
230 replacers = []
231 for handler in underscore_directories:
232 for directory, dirs, files in self.dir_helper.walk(handler.path()):
233 for f in files:
234 replacers.append(
235 self.operations_factory.create_replacer(
236 handler.relative_path(directory, f),
237 handler.new_relative_path(directory, f)
241 # Build list of handlers to process all files
242 filelist = []
243 for root, dirs, files in self.dir_helper.walk(path):
244 for f in files:
245 if f.endswith(".html"):
246 filelist.append(
247 self.handler_factory.create_file_handler(
248 self.file_helper.path_join(root, f),
249 replacers,
250 self.file_helper.open_)
252 if f.endswith(".js"):
253 filelist.append(
254 self.handler_factory.create_file_handler(
255 self.file_helper.path_join(root, f),
256 [self.operations_factory.create_replacer("'_sources/'", "'sources/'")],
257 self.file_helper.open_
261 return Layout(underscore_directories, filelist)
263 def is_underscore_dir(self, path, directory):
265 return (self.dir_helper.is_dir(self.file_helper.path_join(path, directory))
266 and directory.startswith("_"))
270 def sphinx_extension(app, exception):
271 "Wrapped up as a Sphinx Extension"
273 if not app.builder.name in ("html", "dirhtml"):
274 return
276 if not app.config.sphinx_to_github:
277 if app.config.sphinx_to_github_verbose:
278 print "Sphinx-to-github: Disabled, doing nothing."
279 return
281 if exception:
282 if app.config.sphinx_to_github_verbose:
283 print "Sphinx-to-github: Exception raised in main build, doing nothing."
284 return
286 dir_helper = DirHelper(
287 os.path.isdir,
288 os.listdir,
289 os.walk,
290 shutil.rmtree
293 file_helper = FileSystemHelper(
294 open,
295 os.path.join,
296 shutil.move,
297 os.path.exists
300 operations_factory = OperationsFactory()
301 handler_factory = HandlerFactory()
303 layout_factory = LayoutFactory(
304 operations_factory,
305 handler_factory,
306 file_helper,
307 dir_helper,
308 app.config.sphinx_to_github_verbose,
309 sys.stdout,
310 force=True
313 layout = layout_factory.create_layout(app.outdir)
314 layout.process()
317 def setup(app):
318 "Setup function for Sphinx Extension"
320 if (not hasattr(app, 'add_config_value') or
321 not hasattr(app, 'connect')):
322 warnings.warn('Could not call add_config_value() in sphinxtogithub')
323 return
324 app.add_config_value("sphinx_to_github", True, '')
325 app.add_config_value("sphinx_to_github_verbose", True, '')
326 app.connect("build-finished", sphinx_extension)
329 def main(args):
331 usage = "usage: %prog [options] <html directory>"
332 parser = OptionParser(usage=usage)
333 parser.add_option("-v","--verbose", action="store_true",
334 dest="verbose", default=False, help="Provides verbose output")
335 opts, args = parser.parse_args(args)
337 try:
338 path = args[0]
339 except IndexError:
340 sys.stderr.write(
341 "Error - Expecting path to html directory:"
342 "sphinx-to-github <path>\n"
344 return
346 dir_helper = DirHelper(
347 os.path.isdir,
348 os.listdir,
349 os.walk,
350 shutil.rmtree
353 file_helper = FileSystemHelper(
354 open,
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()
378 if __name__ == "__main__":
379 main(sys.argv[1:])