#! /usr/bin/env python """ sphinxtogithub extension Copyright Michael Jones BSD License Original at: https://github.com/michaeljones/sphinx-to-github """ from optparse import OptionParser import os import sys import shutil class DirHelper(object): def __init__(self, is_dir, list_dir, walk, rmtree): self.is_dir = is_dir self.list_dir = list_dir self.walk = walk self.rmtree = rmtree class FileSystemHelper(object): def __init__(self, open_, path_join, move, exists): self.open_ = open_ self.path_join = path_join self.move = move self.exists = exists class Replacer(object): "Encapsulates a simple text replace" def __init__(self, from_, to): self.from_ = from_ self.to = to def process(self, text): return text.replace( self.from_, self.to ) class FileHandler(object): "Applies a series of replacements the contents of a file inplace" def __init__(self, name, replacers, opener): self.name = name self.replacers = replacers self.opener = opener def process(self): text = self.opener(self.name).read() for replacer in self.replacers: text = replacer.process( text ) self.opener(self.name, "w").write(text) class Remover(object): def __init__(self, exists, remove): self.exists = exists self.remove = remove def __call__(self, name): if self.exists(name): self.remove(name) class ForceRename(object): def __init__(self, renamer, remove): self.renamer = renamer self.remove = remove def __call__(self, from_, to): self.remove(to) self.renamer(from_, to) class VerboseRename(object): def __init__(self, renamer, stream): self.renamer = renamer self.stream = stream def __call__(self, from_, to): self.stream.write( "Renaming directory '%s' -> '%s'\n" % (os.path.basename(from_), os.path.basename(to)) ) self.renamer(from_, to) class DirectoryHandler(object): "Encapsulates renaming a directory by removing its first character" def __init__(self, name, root, renamer): self.name = name self.new_name = name[1:] self.root = root + os.sep self.renamer = renamer def path(self): return os.path.join(self.root, self.name) def relative_path(self, directory, filename): path = directory.replace(self.root, "", 1) return os.path.join(path, filename) def new_relative_path(self, directory, filename): path = self.relative_path(directory, filename) return path.replace(self.name, self.new_name, 1) def process(self): from_ = os.path.join(self.root, self.name) to = os.path.join(self.root, self.new_name) self.renamer(from_, to) class HandlerFactory(object): def create_file_handler(self, name, replacers, opener): return FileHandler(name, replacers, opener) def create_dir_handler(self, name, root, renamer): return DirectoryHandler(name, root, renamer) class OperationsFactory(object): def create_force_rename(self, renamer, remover): return ForceRename(renamer, remover) def create_verbose_rename(self, renamer, stream): return VerboseRename(renamer, stream) def create_replacer(self, from_, to): return Replacer(from_, to) def create_remover(self, exists, remove): return Remover(exists, remove) class Layout(object): """ Applies a set of operations which result in the layout of a directory changing """ def __init__(self, directory_handlers, file_handlers): self.directory_handlers = directory_handlers self.file_handlers = file_handlers def process(self): for handler in self.file_handlers: handler.process() for handler in self.directory_handlers: handler.process() class NullLayout(object): """ Layout class that does nothing when asked to process """ def process(self): pass class LayoutFactory(object): "Creates a layout object" def __init__(self, operations_factory, handler_factory, file_helper, dir_helper, verbose, stream, force): self.operations_factory = operations_factory self.handler_factory = handler_factory self.file_helper = file_helper self.dir_helper = dir_helper self.verbose = verbose self.output_stream = stream self.force = force def create_layout(self, path): contents = self.dir_helper.list_dir(path) renamer = self.file_helper.move if self.force: remove = self.operations_factory.create_remover(self.file_helper.exists, self.dir_helper.rmtree) renamer = self.operations_factory.create_force_rename(renamer, remove) if self.verbose: renamer = self.operations_factory.create_verbose_rename(renamer, self.output_stream) # Build list of directories to process directories = [d for d in contents if self.is_underscore_dir(path, d)] underscore_directories = [ self.handler_factory.create_dir_handler(d, path, renamer) for d in directories ] if not underscore_directories: if self.verbose: self.output_stream.write( "No top level directories starting with an underscore " "were found in '%s'\n" % path ) return NullLayout() # Build list of files that are in those directories replacers = [] for handler in underscore_directories: for directory, dirs, files in self.dir_helper.walk(handler.path()): for f in files: replacers.append( self.operations_factory.create_replacer( handler.relative_path(directory, f), handler.new_relative_path(directory, f) ) ) # Build list of handlers to process all files filelist = [] for root, dirs, files in self.dir_helper.walk(path): for f in files: if f.endswith(".html"): filelist.append( self.handler_factory.create_file_handler( self.file_helper.path_join(root, f), replacers, self.file_helper.open_) ) if f.endswith(".js"): filelist.append( self.handler_factory.create_file_handler( self.file_helper.path_join(root, f), [self.operations_factory.create_replacer("'_sources/'", "'sources/'")], self.file_helper.open_ ) ) return Layout(underscore_directories, filelist) def is_underscore_dir(self, path, directory): return (self.dir_helper.is_dir(self.file_helper.path_join(path, directory)) and directory.startswith("_")) def sphinx_extension(app, exception): "Wrapped up as a Sphinx Extension" if not app.builder.name in ("html", "dirhtml"): return if not app.config.sphinx_to_github: if app.config.sphinx_to_github_verbose: print "Sphinx-to-github: Disabled, doing nothing." return if exception: if app.config.sphinx_to_github_verbose: print "Sphinx-to-github: Exception raised in main build, doing nothing." return dir_helper = DirHelper( os.path.isdir, os.listdir, os.walk, shutil.rmtree ) file_helper = FileSystemHelper( open, os.path.join, shutil.move, os.path.exists ) operations_factory = OperationsFactory() handler_factory = HandlerFactory() layout_factory = LayoutFactory( operations_factory, handler_factory, file_helper, dir_helper, app.config.sphinx_to_github_verbose, sys.stdout, force=True ) layout = layout_factory.create_layout(app.outdir) layout.process() def setup(app): "Setup function for Sphinx Extension" app.add_config_value("sphinx_to_github", True, '') app.add_config_value("sphinx_to_github_verbose", True, '') app.connect("build-finished", sphinx_extension) def main(args): usage = "usage: %prog [options] " parser = OptionParser(usage=usage) parser.add_option("-v","--verbose", action="store_true", dest="verbose", default=False, help="Provides verbose output") opts, args = parser.parse_args(args) try: path = args[0] except IndexError: sys.stderr.write( "Error - Expecting path to html directory:" "sphinx-to-github \n" ) return dir_helper = DirHelper( os.path.isdir, os.listdir, os.walk, shutil.rmtree ) file_helper = FileSystemHelper( open, os.path.join, shutil.move, os.path.exists ) operations_factory = OperationsFactory() handler_factory = HandlerFactory() layout_factory = LayoutFactory( operations_factory, handler_factory, file_helper, dir_helper, opts.verbose, sys.stdout, force=False ) layout = layout_factory.create_layout(path) layout.process() if __name__ == "__main__": main(sys.argv[1:])