Makefile: remove references to unused "tags"
[git-cola.git] / extras / sphinxtogithub / sphinxtogithub.py
blob685fcdf559d70f8306cbb2d44f66e5ff8ca6f16f
1 #! /usr/bin/env python
4 from optparse import OptionParser
6 import os
7 import sys
8 import shutil
9 import codecs
12 def stdout(msg):
13 sys.stdout.write(msg + '\n')
16 class DirHelper:
17 def __init__(self, is_dir, list_dir, walk, rmtree):
18 self.is_dir = is_dir
19 self.list_dir = list_dir
20 self.walk = walk
21 self.rmtree = rmtree
24 class FileSystemHelper:
25 def __init__(self, open_, path_join, move, exists):
26 self.open_ = open_
27 self.path_join = path_join
28 self.move = move
29 self.exists = exists
32 class Replacer:
33 """Encapsulates a simple text replace"""
35 def __init__(self, from_, to):
36 self.from_ = from_
37 self.to = to
39 def process(self, text):
40 return text.replace(self.from_, self.to)
43 class FileHandler:
44 """Applies a series of replacements the contents of a file inplace"""
46 def __init__(self, name, replacers, opener):
47 self.name = name
48 self.replacers = replacers
49 self.opener = opener
51 def process(self):
52 text = self.opener(self.name, 'r').read()
54 for replacer in self.replacers:
55 text = replacer.process(text)
57 self.opener(self.name, 'w').write(text)
60 class Remover:
61 def __init__(self, exists, remove):
62 self.exists = exists
63 self.remove = remove
65 def __call__(self, name):
66 if self.exists(name):
67 self.remove(name)
70 class ForceRename:
71 def __init__(self, renamer, remove):
72 self.renamer = renamer
73 self.remove = remove
75 def __call__(self, from_, to):
76 self.remove(to)
77 self.renamer(from_, to)
80 class VerboseRename:
81 def __init__(self, renamer, stream):
82 self.renamer = renamer
83 self.stream = stream
85 def __call__(self, from_, to):
86 self.stream.write(
87 "Renaming directory '%s' -> '%s'\n"
88 % (os.path.basename(from_), os.path.basename(to))
91 self.renamer(from_, to)
94 class DirectoryHandler:
95 """Encapsulates renaming a directory by removing its first character"""
97 def __init__(self, name, root, renamer):
98 self.name = name
99 self.new_name = name[1:]
100 self.root = str(root) + os.sep
101 self.renamer = renamer
103 def path(self):
104 return os.path.join(self.root, self.name)
106 def relative_path(self, directory, filename):
107 path = directory.replace(self.root, '', 1)
108 return os.path.join(path, filename)
110 def new_relative_path(self, directory, filename):
111 path = self.relative_path(directory, filename)
112 return path.replace(self.name, self.new_name, 1)
114 def process(self):
115 from_ = os.path.join(self.root, self.name)
116 to = os.path.join(self.root, self.new_name)
117 self.renamer(from_, to)
120 class HandlerFactory:
121 def create_file_handler(self, name, replacers, opener):
122 return FileHandler(name, replacers, opener)
124 def create_dir_handler(self, name, root, renamer):
125 return DirectoryHandler(name, root, renamer)
128 class OperationsFactory:
129 def create_force_rename(self, renamer, remover):
130 return ForceRename(renamer, remover)
132 def create_verbose_rename(self, renamer, stream):
133 return VerboseRename(renamer, stream)
135 def create_replacer(self, from_, to):
136 return Replacer(from_, to)
138 def create_remover(self, exists, remove):
139 return Remover(exists, remove)
142 class Layout:
144 Applies a set of operations which result in the layout
145 of a directory changing
148 def __init__(self, directory_handlers, file_handlers):
149 self.directory_handlers = directory_handlers
150 self.file_handlers = file_handlers
152 def process(self):
153 for handler in self.file_handlers:
154 handler.process()
156 for handler in self.directory_handlers:
157 handler.process()
160 class NullLayout:
162 Layout class that does nothing when asked to process
165 def process(self):
166 pass
169 class LayoutFactory:
170 """Creates a layout object"""
172 def __init__(
173 self,
174 operations_factory,
175 handler_factory,
176 file_helper,
177 dir_helper,
178 verbose,
179 stream,
180 force,
182 self.operations_factory = operations_factory
183 self.handler_factory = handler_factory
185 self.file_helper = file_helper
186 self.dir_helper = dir_helper
188 self.verbose = verbose
189 self.output_stream = stream
190 self.force = force
192 def create_layout(self, path):
193 contents = self.dir_helper.list_dir(path)
195 renamer = self.file_helper.move
197 if self.force:
198 remove = self.operations_factory.create_remover(
199 self.file_helper.exists, self.dir_helper.rmtree
201 renamer = self.operations_factory.create_force_rename(renamer, remove)
203 if self.verbose:
204 renamer = self.operations_factory.create_verbose_rename(
205 renamer, self.output_stream
208 # Build list of directories to process
209 directories = [d for d in contents if self.is_underscore_dir(path, d)]
210 underscore_directories = [
211 self.handler_factory.create_dir_handler(d, path, renamer)
212 for d in directories
215 if not underscore_directories:
216 if self.verbose:
217 self.output_stream.write(
218 'No top level directories starting with an underscore '
219 "were found in '%s'\n" % path
221 return NullLayout()
223 # Build list of files that are in those directories
224 replacers = []
225 for handler in underscore_directories:
226 for directory, _, files in self.dir_helper.walk(handler.path()):
227 for f in files:
228 replacers.append(
229 self.operations_factory.create_replacer(
230 handler.relative_path(directory, f),
231 handler.new_relative_path(directory, f),
235 # Build list of handlers to process all files
236 filelist = []
237 for root, _, files in self.dir_helper.walk(path):
238 for f in files:
239 if f.endswith('.html'):
240 filelist.append(
241 self.handler_factory.create_file_handler(
242 self.file_helper.path_join(root, f),
243 replacers,
244 self.file_helper.open_,
247 if f.endswith('.js'):
248 filelist.append(
249 self.handler_factory.create_file_handler(
250 self.file_helper.path_join(root, f),
252 self.operations_factory.create_replacer(
253 "'_sources/'", "'sources/'"
256 self.file_helper.open_,
260 return Layout(underscore_directories, filelist)
262 def is_underscore_dir(self, path, directory):
263 return self.dir_helper.is_dir(
264 self.file_helper.path_join(path, directory)
265 ) and directory.startswith('_')
268 def sphinx_extension(app, exception):
269 """Wrapped up as a Sphinx Extension"""
271 if app.builder.name not in ('html', 'dirhtml'):
272 return
274 if not app.config.sphinx_to_github:
275 if app.config.sphinx_to_github_verbose:
276 stdout('Sphinx-to-github: Disabled, doing nothing.')
277 return
279 if exception:
280 if app.config.sphinx_to_github_verbose:
281 msg = 'Sphinx-to-github: ' 'Exception raised in main build, doing nothing.'
282 stdout(msg)
283 return
285 dir_helper = DirHelper(os.path.isdir, os.listdir, os.walk, shutil.rmtree)
287 file_helper = FileSystemHelper(
288 lambda f, mode: codecs.open(f, mode, app.config.sphinx_to_github_encoding),
289 os.path.join,
290 shutil.move,
291 os.path.exists,
294 operations_factory = OperationsFactory()
295 handler_factory = HandlerFactory()
297 layout_factory = LayoutFactory(
298 operations_factory,
299 handler_factory,
300 file_helper,
301 dir_helper,
302 app.config.sphinx_to_github_verbose,
303 sys.stdout,
304 force=True,
307 layout = layout_factory.create_layout(app.outdir)
308 layout.process()
311 def setup(app):
312 """Setup function for Sphinx Extension"""
314 app.add_config_value('sphinx_to_github', True, '')
315 app.add_config_value('sphinx_to_github_verbose', True, '')
316 app.add_config_value('sphinx_to_github_encoding', 'utf-8', '')
318 app.connect('build-finished', sphinx_extension)
321 def main(args):
322 usage = 'usage: %prog [options] <html directory>'
323 parser = OptionParser(usage=usage)
324 parser.add_option(
325 '-v',
326 '--verbose',
327 action='store_true',
328 dest='verbose',
329 default=False,
330 help='Provides verbose output',
332 parser.add_option(
333 '-e',
334 '--encoding',
335 action='store',
336 dest='encoding',
337 default='utf-8',
338 help='Encoding for reading and writing files',
340 opts, args = parser.parse_args(args)
342 try:
343 path = args[0]
344 except IndexError:
345 sys.stderr.write(
346 'Error - Expecting path to html directory:' 'sphinx-to-github <path>\n'
348 return
350 dir_helper = DirHelper(os.path.isdir, os.listdir, os.walk, shutil.rmtree)
352 file_helper = FileSystemHelper(
353 lambda f, mode: codecs.open(f, mode, opts.encoding),
354 os.path.join,
355 shutil.move,
356 os.path.exists,
359 operations_factory = OperationsFactory()
360 handler_factory = HandlerFactory()
362 layout_factory = LayoutFactory(
363 operations_factory,
364 handler_factory,
365 file_helper,
366 dir_helper,
367 opts.verbose,
368 sys.stdout,
369 force=False,
372 layout = layout_factory.create_layout(path)
373 layout.process()
376 if __name__ == '__main__':
377 main(sys.argv[1:])