this repo has no description
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