412 lines
12 KiB
Python
Executable File
412 lines
12 KiB
Python
Executable File
#! /usr/bin/env python
|
|
|
|
# // BSD licence, modified to remove the organisation as there isn't one.
|
|
#
|
|
# Copyright (c) 2009, Michael Jones
|
|
# All rights reserved.
|
|
#
|
|
# Redistribution and use in source and binary forms, with or without modification,
|
|
# are permitted provided that the following conditions are met:
|
|
#
|
|
# * Redistributions of source code must retain the above copyright notice,
|
|
# this list of conditions and the following disclaimer.
|
|
# * Redistributions in binary form must reproduce the above copyright notice,
|
|
# this list of conditions and the following disclaimer in the documentation
|
|
# and/or other materials provided with the distribution.
|
|
# * The names of its contributors may not be used to endorse or promote
|
|
# products derived from this software without specific prior written
|
|
# permission.
|
|
#
|
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
|
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
|
|
from optparse import OptionParser
|
|
|
|
import os
|
|
import sys
|
|
import shutil
|
|
import codecs
|
|
|
|
|
|
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, "r").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(
|
|
lambda f, mode: codecs.open(f, mode, app.config.sphinx_to_github_encoding),
|
|
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.add_config_value("sphinx_to_github_encoding", 'utf-8', '')
|
|
|
|
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")
|
|
parser.add_option("-e","--encoding", action="store",
|
|
dest="encoding", default="utf-8", help="Encoding for reading and writing files")
|
|
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(
|
|
lambda f, mode: codecs.open(f, mode, opts.encoding),
|
|
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:])
|
|
|
|
|