text: #! /usr/bin/env python
   
  from optparse import OptionParser
  import os
  import sys
  import shutil
  
  
  class NoDirectoriesError(Exception):
      "Error thrown when no directories starting with an underscore are found"
  
  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 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:
              raise NoDirectoriesError()
  
          # 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
              )
  
      try:
          layout = layout_factory.create_layout(path)
      except NoDirectoriesError:
          sys.stderr.write(
                  "Error - No top level directories starting with an underscore "
                  "were found in '%s'\n" % path
                  )
          return
  
      layout.process()
      
  
  
  if __name__ == "__main__":
      main(sys.argv[1:])
  
  
  
  