sphinxtogithub.py
381 lines
| 9.9 KiB
| text/x-python
|
PythonLexer
MinRK
|
r3255 | #! /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] <html directory>" | ||||
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 <path>\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:]) | ||||