3 from optparse
import OptionParser
10 class DirHelper(object):
12 def __init__(self
, is_dir
, list_dir
, walk
, rmtree
):
15 self
.list_dir
= list_dir
19 class FileSystemHelper(object):
21 def __init__(self
, open_
, path_join
, move
, exists
):
24 self
.path_join
= path_join
28 class Replacer(object):
29 "Encapsulates a simple text replace"
31 def __init__(self
, from_
, to
):
36 def process(self
, text
):
38 return text
.replace( self
.from_
, self
.to
)
40 class FileHandler(object):
41 "Applies a series of replacements the contents of a file inplace"
43 def __init__(self
, name
, replacers
, opener
):
46 self
.replacers
= replacers
51 text
= self
.opener(self
.name
).read()
53 for replacer
in self
.replacers
:
54 text
= replacer
.process( text
)
56 self
.opener(self
.name
, "w").write(text
)
58 class Remover(object):
60 def __init__(self
, exists
, remove
):
64 def __call__(self
, name
):
69 class ForceRename(object):
71 def __init__(self
, renamer
, remove
):
73 self
.renamer
= renamer
76 def __call__(self
, from_
, to
):
79 self
.renamer(from_
, to
)
81 class VerboseRename(object):
83 def __init__(self
, renamer
, stream
):
85 self
.renamer
= renamer
88 def __call__(self
, from_
, to
):
91 "Renaming directory '%s' -> '%s'\n"
92 % (os
.path
.basename(from_
), os
.path
.basename(to
))
95 self
.renamer(from_
, to
)
98 class DirectoryHandler(object):
99 "Encapsulates renaming a directory by removing its first character"
101 def __init__(self
, name
, root
, renamer
):
104 self
.new_name
= name
[1:]
105 self
.root
= root
+ os
.sep
106 self
.renamer
= renamer
110 return os
.path
.join(self
.root
, self
.name
)
112 def relative_path(self
, directory
, filename
):
114 path
= directory
.replace(self
.root
, "", 1)
115 return os
.path
.join(path
, filename
)
117 def new_relative_path(self
, directory
, filename
):
119 path
= self
.relative_path(directory
, filename
)
120 return path
.replace(self
.name
, self
.new_name
, 1)
124 from_
= os
.path
.join(self
.root
, self
.name
)
125 to
= os
.path
.join(self
.root
, self
.new_name
)
126 self
.renamer(from_
, to
)
129 class HandlerFactory(object):
131 def create_file_handler(self
, name
, replacers
, opener
):
133 return FileHandler(name
, replacers
, opener
)
135 def create_dir_handler(self
, name
, root
, renamer
):
137 return DirectoryHandler(name
, root
, renamer
)
140 class OperationsFactory(object):
142 def create_force_rename(self
, renamer
, remover
):
144 return ForceRename(renamer
, remover
)
146 def create_verbose_rename(self
, renamer
, stream
):
148 return VerboseRename(renamer
, stream
)
150 def create_replacer(self
, from_
, to
):
152 return Replacer(from_
, to
)
154 def create_remover(self
, exists
, remove
):
156 return Remover(exists
, remove
)
159 class Layout(object):
161 Applies a set of operations which result in the layout
162 of a directory changing
165 def __init__(self
, directory_handlers
, file_handlers
):
167 self
.directory_handlers
= directory_handlers
168 self
.file_handlers
= file_handlers
172 for handler
in self
.file_handlers
:
175 for handler
in self
.directory_handlers
:
179 class NullLayout(object):
181 Layout class that does nothing when asked to process
186 class LayoutFactory(object):
187 "Creates a layout object"
189 def __init__(self
, operations_factory
, handler_factory
, file_helper
, dir_helper
, verbose
, stream
, force
):
191 self
.operations_factory
= operations_factory
192 self
.handler_factory
= handler_factory
194 self
.file_helper
= file_helper
195 self
.dir_helper
= dir_helper
197 self
.verbose
= verbose
198 self
.output_stream
= stream
201 def create_layout(self
, path
):
203 contents
= self
.dir_helper
.list_dir(path
)
205 renamer
= self
.file_helper
.move
208 remove
= self
.operations_factory
.create_remover(self
.file_helper
.exists
, self
.dir_helper
.rmtree
)
209 renamer
= self
.operations_factory
.create_force_rename(renamer
, remove
)
212 renamer
= self
.operations_factory
.create_verbose_rename(renamer
, self
.output_stream
)
214 # Build list of directories to process
215 directories
= [d
for d
in contents
if self
.is_underscore_dir(path
, d
)]
216 underscore_directories
= [
217 self
.handler_factory
.create_dir_handler(d
, path
, renamer
)
221 if not underscore_directories
:
223 self
.output_stream
.write(
224 "No top level directories starting with an underscore "
225 "were found in '%s'\n" % path
229 # Build list of files that are in those directories
231 for handler
in underscore_directories
:
232 for directory
, dirs
, files
in self
.dir_helper
.walk(handler
.path()):
235 self
.operations_factory
.create_replacer(
236 handler
.relative_path(directory
, f
),
237 handler
.new_relative_path(directory
, f
)
241 # Build list of handlers to process all files
243 for root
, dirs
, files
in self
.dir_helper
.walk(path
):
245 if f
.endswith(".html"):
247 self
.handler_factory
.create_file_handler(
248 self
.file_helper
.path_join(root
, f
),
250 self
.file_helper
.open_
)
252 if f
.endswith(".js"):
254 self
.handler_factory
.create_file_handler(
255 self
.file_helper
.path_join(root
, f
),
256 [self
.operations_factory
.create_replacer("'_sources/'", "'sources/'")],
257 self
.file_helper
.open_
261 return Layout(underscore_directories
, filelist
)
263 def is_underscore_dir(self
, path
, directory
):
265 return (self
.dir_helper
.is_dir(self
.file_helper
.path_join(path
, directory
))
266 and directory
.startswith("_"))
270 def sphinx_extension(app
, exception
):
271 "Wrapped up as a Sphinx Extension"
273 if not app
.builder
.name
in ("html", "dirhtml"):
276 if not app
.config
.sphinx_to_github
:
277 if app
.config
.sphinx_to_github_verbose
:
278 print "Sphinx-to-github: Disabled, doing nothing."
282 if app
.config
.sphinx_to_github_verbose
:
283 print "Sphinx-to-github: Exception raised in main build, doing nothing."
286 dir_helper
= DirHelper(
293 file_helper
= FileSystemHelper(
300 operations_factory
= OperationsFactory()
301 handler_factory
= HandlerFactory()
303 layout_factory
= LayoutFactory(
308 app
.config
.sphinx_to_github_verbose
,
313 layout
= layout_factory
.create_layout(app
.outdir
)
318 "Setup function for Sphinx Extension"
320 if (not hasattr(app
, 'add_config_value') or
321 not hasattr(app
, 'connect')):
322 warnings
.warn('Could not call add_config_value() in sphinxtogithub')
324 app
.add_config_value("sphinx_to_github", True, '')
325 app
.add_config_value("sphinx_to_github_verbose", True, '')
326 app
.connect("build-finished", sphinx_extension
)
331 usage
= "usage: %prog [options] <html directory>"
332 parser
= OptionParser(usage
=usage
)
333 parser
.add_option("-v","--verbose", action
="store_true",
334 dest
="verbose", default
=False, help="Provides verbose output")
335 opts
, args
= parser
.parse_args(args
)
341 "Error - Expecting path to html directory:"
342 "sphinx-to-github <path>\n"
346 dir_helper
= DirHelper(
353 file_helper
= FileSystemHelper(
360 operations_factory
= OperationsFactory()
361 handler_factory
= HandlerFactory()
363 layout_factory
= LayoutFactory(
373 layout
= layout_factory
.create_layout(path
)
378 if __name__
== "__main__":