this repo has no description
at develop 12 kB view raw
1#! /usr/bin/env python 2 3# // BSD licence, modified to remove the organisation as there isn't one. 4# 5# Copyright (c) 2009, Michael Jones 6# All rights reserved. 7# 8# Redistribution and use in source and binary forms, with or without modification, 9# are permitted provided that the following conditions are met: 10# 11# * Redistributions of source code must retain the above copyright notice, 12# this list of conditions and the following disclaimer. 13# * Redistributions in binary form must reproduce the above copyright notice, 14# this list of conditions and the following disclaimer in the documentation 15# and/or other materials provided with the distribution. 16# * The names of its contributors may not be used to endorse or promote 17# products derived from this software without specific prior written 18# permission. 19# 20# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 21# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 22# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 24# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 25# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 26# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 27# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 31 32from optparse import OptionParser 33 34import os 35import sys 36import shutil 37import codecs 38 39 40class DirHelper(object): 41 42 def __init__(self, is_dir, list_dir, walk, rmtree): 43 44 self.is_dir = is_dir 45 self.list_dir = list_dir 46 self.walk = walk 47 self.rmtree = rmtree 48 49class FileSystemHelper(object): 50 51 def __init__(self, open_, path_join, move, exists): 52 53 self.open_ = open_ 54 self.path_join = path_join 55 self.move = move 56 self.exists = exists 57 58class Replacer(object): 59 "Encapsulates a simple text replace" 60 61 def __init__(self, from_, to): 62 63 self.from_ = from_ 64 self.to = to 65 66 def process(self, text): 67 68 return text.replace( self.from_, self.to ) 69 70class FileHandler(object): 71 "Applies a series of replacements the contents of a file inplace" 72 73 def __init__(self, name, replacers, opener): 74 75 self.name = name 76 self.replacers = replacers 77 self.opener = opener 78 79 def process(self): 80 81 text = self.opener(self.name, "r").read() 82 83 for replacer in self.replacers: 84 text = replacer.process( text ) 85 86 self.opener(self.name, "w").write(text) 87 88class Remover(object): 89 90 def __init__(self, exists, remove): 91 self.exists = exists 92 self.remove = remove 93 94 def __call__(self, name): 95 96 if self.exists(name): 97 self.remove(name) 98 99class ForceRename(object): 100 101 def __init__(self, renamer, remove): 102 103 self.renamer = renamer 104 self.remove = remove 105 106 def __call__(self, from_, to): 107 108 self.remove(to) 109 self.renamer(from_, to) 110 111class VerboseRename(object): 112 113 def __init__(self, renamer, stream): 114 115 self.renamer = renamer 116 self.stream = stream 117 118 def __call__(self, from_, to): 119 120 self.stream.write( 121 "Renaming directory '%s' -> '%s'\n" 122 % (os.path.basename(from_), os.path.basename(to)) 123 ) 124 125 self.renamer(from_, to) 126 127 128class DirectoryHandler(object): 129 "Encapsulates renaming a directory by removing its first character" 130 131 def __init__(self, name, root, renamer): 132 133 self.name = name 134 self.new_name = name[1:] 135 self.root = root + os.sep 136 self.renamer = renamer 137 138 def path(self): 139 140 return os.path.join(self.root, self.name) 141 142 def relative_path(self, directory, filename): 143 144 path = directory.replace(self.root, "", 1) 145 return os.path.join(path, filename) 146 147 def new_relative_path(self, directory, filename): 148 149 path = self.relative_path(directory, filename) 150 return path.replace(self.name, self.new_name, 1) 151 152 def process(self): 153 154 from_ = os.path.join(self.root, self.name) 155 to = os.path.join(self.root, self.new_name) 156 self.renamer(from_, to) 157 158 159class HandlerFactory(object): 160 161 def create_file_handler(self, name, replacers, opener): 162 163 return FileHandler(name, replacers, opener) 164 165 def create_dir_handler(self, name, root, renamer): 166 167 return DirectoryHandler(name, root, renamer) 168 169 170class OperationsFactory(object): 171 172 def create_force_rename(self, renamer, remover): 173 174 return ForceRename(renamer, remover) 175 176 def create_verbose_rename(self, renamer, stream): 177 178 return VerboseRename(renamer, stream) 179 180 def create_replacer(self, from_, to): 181 182 return Replacer(from_, to) 183 184 def create_remover(self, exists, remove): 185 186 return Remover(exists, remove) 187 188 189class Layout(object): 190 """ 191 Applies a set of operations which result in the layout 192 of a directory changing 193 """ 194 195 def __init__(self, directory_handlers, file_handlers): 196 197 self.directory_handlers = directory_handlers 198 self.file_handlers = file_handlers 199 200 def process(self): 201 202 for handler in self.file_handlers: 203 handler.process() 204 205 for handler in self.directory_handlers: 206 handler.process() 207 208 209class NullLayout(object): 210 """ 211 Layout class that does nothing when asked to process 212 """ 213 def process(self): 214 pass 215 216class LayoutFactory(object): 217 "Creates a layout object" 218 219 def __init__(self, operations_factory, handler_factory, file_helper, dir_helper, verbose, stream, force): 220 221 self.operations_factory = operations_factory 222 self.handler_factory = handler_factory 223 224 self.file_helper = file_helper 225 self.dir_helper = dir_helper 226 227 self.verbose = verbose 228 self.output_stream = stream 229 self.force = force 230 231 def create_layout(self, path): 232 233 contents = self.dir_helper.list_dir(path) 234 235 renamer = self.file_helper.move 236 237 if self.force: 238 remove = self.operations_factory.create_remover(self.file_helper.exists, self.dir_helper.rmtree) 239 renamer = self.operations_factory.create_force_rename(renamer, remove) 240 241 if self.verbose: 242 renamer = self.operations_factory.create_verbose_rename(renamer, self.output_stream) 243 244 # Build list of directories to process 245 directories = [d for d in contents if self.is_underscore_dir(path, d)] 246 underscore_directories = [ 247 self.handler_factory.create_dir_handler(d, path, renamer) 248 for d in directories 249 ] 250 251 if not underscore_directories: 252 if self.verbose: 253 self.output_stream.write( 254 "No top level directories starting with an underscore " 255 "were found in '%s'\n" % path 256 ) 257 return NullLayout() 258 259 # Build list of files that are in those directories 260 replacers = [] 261 for handler in underscore_directories: 262 for directory, dirs, files in self.dir_helper.walk(handler.path()): 263 for f in files: 264 replacers.append( 265 self.operations_factory.create_replacer( 266 handler.relative_path(directory, f), 267 handler.new_relative_path(directory, f) 268 ) 269 ) 270 271 # Build list of handlers to process all files 272 filelist = [] 273 for root, dirs, files in self.dir_helper.walk(path): 274 for f in files: 275 if f.endswith(".html"): 276 filelist.append( 277 self.handler_factory.create_file_handler( 278 self.file_helper.path_join(root, f), 279 replacers, 280 self.file_helper.open_) 281 ) 282 if f.endswith(".js"): 283 filelist.append( 284 self.handler_factory.create_file_handler( 285 self.file_helper.path_join(root, f), 286 [self.operations_factory.create_replacer("'_sources/'", "'sources/'")], 287 self.file_helper.open_ 288 ) 289 ) 290 291 return Layout(underscore_directories, filelist) 292 293 def is_underscore_dir(self, path, directory): 294 295 return (self.dir_helper.is_dir(self.file_helper.path_join(path, directory)) 296 and directory.startswith("_")) 297 298 299 300def sphinx_extension(app, exception): 301 "Wrapped up as a Sphinx Extension" 302 303 if not app.builder.name in ("html", "dirhtml"): 304 return 305 306 if not app.config.sphinx_to_github: 307 if app.config.sphinx_to_github_verbose: 308 print("Sphinx-to-github: Disabled, doing nothing.") 309 return 310 311 if exception: 312 if app.config.sphinx_to_github_verbose: 313 print("Sphinx-to-github: Exception raised in main build, doing nothing.") 314 return 315 316 dir_helper = DirHelper( 317 os.path.isdir, 318 os.listdir, 319 os.walk, 320 shutil.rmtree 321 ) 322 323 file_helper = FileSystemHelper( 324 lambda f, mode: codecs.open(f, mode, app.config.sphinx_to_github_encoding), 325 os.path.join, 326 shutil.move, 327 os.path.exists 328 ) 329 330 operations_factory = OperationsFactory() 331 handler_factory = HandlerFactory() 332 333 layout_factory = LayoutFactory( 334 operations_factory, 335 handler_factory, 336 file_helper, 337 dir_helper, 338 app.config.sphinx_to_github_verbose, 339 sys.stdout, 340 force=True 341 ) 342 343 layout = layout_factory.create_layout(app.outdir) 344 layout.process() 345 346 347def setup(app): 348 "Setup function for Sphinx Extension" 349 350 app.add_config_value("sphinx_to_github", True, '') 351 app.add_config_value("sphinx_to_github_verbose", True, '') 352 app.add_config_value("sphinx_to_github_encoding", 'utf-8', '') 353 354 app.connect("build-finished", sphinx_extension) 355 356 357def main(args): 358 359 usage = "usage: %prog [options] <html directory>" 360 parser = OptionParser(usage=usage) 361 parser.add_option("-v","--verbose", action="store_true", 362 dest="verbose", default=False, help="Provides verbose output") 363 parser.add_option("-e","--encoding", action="store", 364 dest="encoding", default="utf-8", help="Encoding for reading and writing files") 365 opts, args = parser.parse_args(args) 366 367 try: 368 path = args[0] 369 except IndexError: 370 sys.stderr.write( 371 "Error - Expecting path to html directory:" 372 "sphinx-to-github <path>\n" 373 ) 374 return 375 376 dir_helper = DirHelper( 377 os.path.isdir, 378 os.listdir, 379 os.walk, 380 shutil.rmtree 381 ) 382 383 file_helper = FileSystemHelper( 384 lambda f, mode: codecs.open(f, mode, opts.encoding), 385 os.path.join, 386 shutil.move, 387 os.path.exists 388 ) 389 390 operations_factory = OperationsFactory() 391 handler_factory = HandlerFactory() 392 393 layout_factory = LayoutFactory( 394 operations_factory, 395 handler_factory, 396 file_helper, 397 dir_helper, 398 opts.verbose, 399 sys.stdout, 400 force=False 401 ) 402 403 layout = layout_factory.create_layout(path) 404 layout.process() 405 406 407 408if __name__ == "__main__": 409 main(sys.argv[1:]) 410 411