├── .gitignore ├── LICENCE ├── MANIFEST.in ├── README.rst ├── bin └── sphinxtogithub ├── setup.py └── sphinxtogithub ├── __init__.py ├── sphinxtogithub.py └── tests ├── __init__.py ├── directoryhandler.py ├── filehandler.py ├── layout.py ├── layoutfactory.py ├── remover.py ├── renamer.py ├── replacer.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | // BSD licence, modified to remove the organisation as there isn't one. 2 | 3 | Copyright (c) 2009, Michael Jones 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without modification, 7 | are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | * The names of its contributors may not be used to endorse or promote 15 | products derived from this software without specific prior written 16 | permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENCE 2 | include README.rst 3 | recursive-include sphinxtogithub *.py 4 | prune sphinxtogithub/*.pyc -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Sphinx to GitHub 2 | ================ 3 | 4 | ATTENTION! 5 | ---------- 6 | 7 | This project is designed to help you get around the github-pages Jekyll 8 | behaviour of ignoring top level directories starting with an underscore. 9 | 10 | This is solved in a much neater way by creating a ``.nojekyll`` in the root 11 | of you github-pages which will disable Jekyll as described `here 12 | `__ and `here 13 | `__. 14 | 15 | This makes this project largely useless! Thank you to `acdha 16 | `__ for making me aware of this. 17 | 18 | What? 19 | ----- 20 | 21 | A Python script for preparing the html output of the Sphinx documentation 22 | system for github pages. 23 | 24 | It renames any top level folders which start with an underscore and edits any 25 | references to them within the html files. 26 | 27 | Why? 28 | ---- 29 | 30 | GitHub processes the incoming html with Jekyll which believes top level folders 31 | starting with an underscore are special and does not let their content be accessible 32 | to the server. This is incompatible with Sphinx which uses underscores at the 33 | start of folder names for static content. 34 | 35 | Usage 36 | ----- 37 | 38 | The ``sphinxtogithub.py`` script can be run on the command line or used as a 39 | Sphinx extension. 40 | 41 | Extension 42 | ~~~~~~~~~ 43 | 44 | Place the script on the ``PYTHONPATH`` and add ``sphinxtogithub`` to the 45 | extensions list in the ``conf.py`` file in your Sphinx project:: 46 | 47 | extensions = [ "sphinxtogithub" ] 48 | 49 | Additionally there are three config variables you can use to control the 50 | extension. The first enables/disables the extension, the second enables verbose 51 | output and the third determines the encoding which is used to read & write 52 | files. The first two are ``True`` by default and the third is set to ``utf-8``:: 53 | 54 | sphinx_to_github = True 55 | sphinx_to_github_verbose = True 56 | sphinx_to_github_encoding = "utf-8" 57 | 58 | Command Line 59 | ~~~~~~~~~~~~ 60 | 61 | Run the script with the path to the ``html`` output directory as the first 62 | argument. There is a ``--verbose`` flag for basic output. 63 | 64 | Further Information 65 | ------------------- 66 | 67 | Install from GitHub 68 | ~~~~~~~~~~~~~~~~~~~ 69 | 70 | It should be possible to install this tool directly from github using pip:: 71 | 72 | pip install -e git+git://github.com/michaeljones/sphinx-to-github.git#egg=sphinx-to-github 73 | 74 | Thanks to `winhamwr `_'s work. 75 | 76 | Requirements 77 | ~~~~~~~~~~~~ 78 | 79 | The script uses ``/usr/bin/env`` and ``python``. 80 | 81 | Running Tests 82 | ~~~~~~~~~~~~~ 83 | 84 | Unit tests can be run using the setuptools ``test`` target. eg:: 85 | 86 | $ python setup.py test 87 | 88 | Alternatives 89 | ~~~~~~~~~~~~ 90 | 91 | `dinoboff `_'s project 92 | `github-tools `_ provides similar 93 | functionality combined with a much more comprehensive set of tools for helping 94 | you to manage Python based projects on github. 95 | 96 | Credits 97 | ------- 98 | 99 | Thank you to: 100 | 101 | * `mikejs `_ 102 | * `certik `_ 103 | * `davvid `_ 104 | * `winhamwr `_ 105 | * `johnpaulett `_ 106 | * `boothead `_ 107 | * `kennethreitz `_ 108 | * `acdha `_ 109 | * `garbados `_ 110 | 111 | For their contributions, which are beginning to outweigh mine, to Georg Brandl 112 | for `Sphinx `_ and the github crew for the pages 113 | functionality. 114 | 115 | 116 | -------------------------------------------------------------------------------- /bin/sphinxtogithub: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import sys 3 | from sphinxtogithub.sphinxtogithub import main 4 | 5 | if __name__ == "__main__": 6 | main(sys.argv[1:]) -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import codecs 5 | import os 6 | import unittest 7 | 8 | try: 9 | from setuptools import setup, find_packages, Command 10 | except ImportError: 11 | from ez_setup import use_setuptools 12 | use_setuptools() 13 | from setuptools import setup, find_packages, Command 14 | 15 | import sphinxtogithub 16 | from sphinxtogithub.tests import ( 17 | filehandler, 18 | directoryhandler, 19 | replacer, 20 | renamer, 21 | remover, 22 | layout, 23 | layoutfactory, 24 | setup as setuptest, 25 | ) 26 | 27 | class RunTests(Command): 28 | description = "Run the sphinxtogithub test suite." 29 | 30 | user_options = [] 31 | 32 | def initialize_options(self): 33 | pass 34 | 35 | def finalize_options(self): 36 | pass 37 | 38 | def run(self): 39 | suites = [ 40 | filehandler.testSuite(), 41 | directoryhandler.testSuite(), 42 | replacer.testSuite(), 43 | renamer.testSuite(), 44 | remover.testSuite(), 45 | layout.testSuite(), 46 | layoutfactory.testSuite(), 47 | setuptest.testSuite(), 48 | ] 49 | 50 | suite = unittest.TestSuite(suites) 51 | 52 | runner = unittest.TextTestRunner() 53 | 54 | runner.run(suite) 55 | 56 | 57 | class Publish(Command): 58 | description = "Publish package to PyPi" 59 | 60 | user_options = [] 61 | 62 | def initialize_options(self): 63 | pass 64 | 65 | def finalize_options(self): 66 | pass 67 | 68 | def run(self): 69 | """Publish to PyPi""" 70 | 71 | os.system("python setup.py sdist upload") 72 | 73 | 74 | long_description = codecs.open("README.rst", "r", "utf-8").read() 75 | 76 | setup( 77 | name='sphinxtogithub', 78 | version=sphinxtogithub.__version__, 79 | description=sphinxtogithub.__doc__, 80 | author=sphinxtogithub.__author__, 81 | author_email=sphinxtogithub.__contact__, 82 | url=sphinxtogithub.__homepage__, 83 | platforms=["any"], 84 | license="BSD", 85 | packages=find_packages(), 86 | scripts=["bin/sphinxtogithub"], 87 | zip_safe=False, 88 | install_requires=[], 89 | cmdclass = {"test": RunTests, "publish" : Publish}, 90 | classifiers=[ 91 | "Development Status :: 4 - Beta", 92 | "Operating System :: OS Independent", 93 | "Programming Language :: Python", 94 | "Environment :: Plugins", 95 | "Intended Audience :: Developers", 96 | "License :: OSI Approved :: BSD License", 97 | "Operating System :: POSIX", 98 | "Topic :: Documentation", 99 | ], 100 | long_description=long_description, 101 | ) 102 | -------------------------------------------------------------------------------- /sphinxtogithub/__init__.py: -------------------------------------------------------------------------------- 1 | """Script for preparing the html output of the Sphinx documentation system for 2 | github pages. """ 3 | 4 | VERSION = (1, 1, 0, 'dev') 5 | 6 | __version__ = ".".join(map(str, VERSION[:-1])) 7 | __release__ = ".".join(map(str, VERSION)) 8 | __author__ = "Michael Jones" 9 | __contact__ = "http://github.com/michaeljones" 10 | __homepage__ = "http://github.com/michaeljones/sphinx-to-github" 11 | __docformat__ = "restructuredtext" 12 | 13 | from sphinxtogithub import ( 14 | setup, 15 | sphinx_extension, 16 | LayoutFactory, 17 | Layout, 18 | DirectoryHandler, 19 | VerboseRename, 20 | ForceRename, 21 | Remover, 22 | FileHandler, 23 | Replacer, 24 | DirHelper, 25 | FileSystemHelper, 26 | OperationsFactory, 27 | HandlerFactory 28 | ) 29 | 30 | -------------------------------------------------------------------------------- /sphinxtogithub/sphinxtogithub.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | from optparse import OptionParser 4 | 5 | import os 6 | import sys 7 | import shutil 8 | import codecs 9 | 10 | 11 | class DirHelper(object): 12 | 13 | def __init__(self, is_dir, list_dir, walk, rmtree): 14 | 15 | self.is_dir = is_dir 16 | self.list_dir = list_dir 17 | self.walk = walk 18 | self.rmtree = rmtree 19 | 20 | class FileSystemHelper(object): 21 | 22 | def __init__(self, open_, path_join, move, exists): 23 | 24 | self.open_ = open_ 25 | self.path_join = path_join 26 | self.move = move 27 | self.exists = exists 28 | 29 | class Replacer(object): 30 | "Encapsulates a simple text replace" 31 | 32 | def __init__(self, from_, to): 33 | 34 | self.from_ = from_ 35 | self.to = to 36 | 37 | def process(self, text): 38 | 39 | return text.replace( self.from_, self.to ) 40 | 41 | class FileHandler(object): 42 | "Applies a series of replacements the contents of a file inplace" 43 | 44 | def __init__(self, name, replacers, opener): 45 | 46 | self.name = name 47 | self.replacers = replacers 48 | self.opener = opener 49 | 50 | def process(self): 51 | 52 | text = self.opener(self.name, "r").read() 53 | 54 | for replacer in self.replacers: 55 | text = replacer.process( text ) 56 | 57 | self.opener(self.name, "w").write(text) 58 | 59 | class Remover(object): 60 | 61 | def __init__(self, exists, remove): 62 | self.exists = exists 63 | self.remove = remove 64 | 65 | def __call__(self, name): 66 | 67 | if self.exists(name): 68 | self.remove(name) 69 | 70 | class ForceRename(object): 71 | 72 | def __init__(self, renamer, remove): 73 | 74 | self.renamer = renamer 75 | self.remove = remove 76 | 77 | def __call__(self, from_, to): 78 | 79 | self.remove(to) 80 | self.renamer(from_, to) 81 | 82 | class VerboseRename(object): 83 | 84 | def __init__(self, renamer, stream): 85 | 86 | self.renamer = renamer 87 | self.stream = stream 88 | 89 | def __call__(self, from_, to): 90 | 91 | self.stream.write( 92 | "Renaming directory '%s' -> '%s'\n" 93 | % (os.path.basename(from_), os.path.basename(to)) 94 | ) 95 | 96 | self.renamer(from_, to) 97 | 98 | 99 | class DirectoryHandler(object): 100 | "Encapsulates renaming a directory by removing its first character" 101 | 102 | def __init__(self, name, root, renamer): 103 | 104 | self.name = name 105 | self.new_name = name[1:] 106 | self.root = root + os.sep 107 | self.renamer = renamer 108 | 109 | def path(self): 110 | 111 | return os.path.join(self.root, self.name) 112 | 113 | def relative_path(self, directory, filename): 114 | 115 | path = directory.replace(self.root, "", 1) 116 | return os.path.join(path, filename) 117 | 118 | def new_relative_path(self, directory, filename): 119 | 120 | path = self.relative_path(directory, filename) 121 | return path.replace(self.name, self.new_name, 1) 122 | 123 | def process(self): 124 | 125 | from_ = os.path.join(self.root, self.name) 126 | to = os.path.join(self.root, self.new_name) 127 | self.renamer(from_, to) 128 | 129 | 130 | class HandlerFactory(object): 131 | 132 | def create_file_handler(self, name, replacers, opener): 133 | 134 | return FileHandler(name, replacers, opener) 135 | 136 | def create_dir_handler(self, name, root, renamer): 137 | 138 | return DirectoryHandler(name, root, renamer) 139 | 140 | 141 | class OperationsFactory(object): 142 | 143 | def create_force_rename(self, renamer, remover): 144 | 145 | return ForceRename(renamer, remover) 146 | 147 | def create_verbose_rename(self, renamer, stream): 148 | 149 | return VerboseRename(renamer, stream) 150 | 151 | def create_replacer(self, from_, to): 152 | 153 | return Replacer(from_, to) 154 | 155 | def create_remover(self, exists, remove): 156 | 157 | return Remover(exists, remove) 158 | 159 | 160 | class Layout(object): 161 | """ 162 | Applies a set of operations which result in the layout 163 | of a directory changing 164 | """ 165 | 166 | def __init__(self, directory_handlers, file_handlers): 167 | 168 | self.directory_handlers = directory_handlers 169 | self.file_handlers = file_handlers 170 | 171 | def process(self): 172 | 173 | for handler in self.file_handlers: 174 | handler.process() 175 | 176 | for handler in self.directory_handlers: 177 | handler.process() 178 | 179 | 180 | class NullLayout(object): 181 | """ 182 | Layout class that does nothing when asked to process 183 | """ 184 | def process(self): 185 | pass 186 | 187 | class LayoutFactory(object): 188 | "Creates a layout object" 189 | 190 | def __init__(self, operations_factory, handler_factory, file_helper, dir_helper, verbose, stream, force): 191 | 192 | self.operations_factory = operations_factory 193 | self.handler_factory = handler_factory 194 | 195 | self.file_helper = file_helper 196 | self.dir_helper = dir_helper 197 | 198 | self.verbose = verbose 199 | self.output_stream = stream 200 | self.force = force 201 | 202 | def create_layout(self, path): 203 | 204 | contents = self.dir_helper.list_dir(path) 205 | 206 | renamer = self.file_helper.move 207 | 208 | if self.force: 209 | remove = self.operations_factory.create_remover(self.file_helper.exists, self.dir_helper.rmtree) 210 | renamer = self.operations_factory.create_force_rename(renamer, remove) 211 | 212 | if self.verbose: 213 | renamer = self.operations_factory.create_verbose_rename(renamer, self.output_stream) 214 | 215 | # Build list of directories to process 216 | directories = [d for d in contents if self.is_underscore_dir(path, d)] 217 | underscore_directories = [ 218 | self.handler_factory.create_dir_handler(d, path, renamer) 219 | for d in directories 220 | ] 221 | 222 | if not underscore_directories: 223 | if self.verbose: 224 | self.output_stream.write( 225 | "No top level directories starting with an underscore " 226 | "were found in '%s'\n" % path 227 | ) 228 | return NullLayout() 229 | 230 | # Build list of files that are in those directories 231 | replacers = [] 232 | for handler in underscore_directories: 233 | for directory, dirs, files in self.dir_helper.walk(handler.path()): 234 | for f in files: 235 | replacers.append( 236 | self.operations_factory.create_replacer( 237 | handler.relative_path(directory, f), 238 | handler.new_relative_path(directory, f) 239 | ) 240 | ) 241 | 242 | # Build list of handlers to process all files 243 | filelist = [] 244 | for root, dirs, files in self.dir_helper.walk(path): 245 | for f in files: 246 | if f.endswith(".html"): 247 | filelist.append( 248 | self.handler_factory.create_file_handler( 249 | self.file_helper.path_join(root, f), 250 | replacers, 251 | self.file_helper.open_) 252 | ) 253 | if f.endswith(".js"): 254 | filelist.append( 255 | self.handler_factory.create_file_handler( 256 | self.file_helper.path_join(root, f), 257 | [self.operations_factory.create_replacer("'_sources/'", "'sources/'")], 258 | self.file_helper.open_ 259 | ) 260 | ) 261 | 262 | return Layout(underscore_directories, filelist) 263 | 264 | def is_underscore_dir(self, path, directory): 265 | 266 | return (self.dir_helper.is_dir(self.file_helper.path_join(path, directory)) 267 | and directory.startswith("_")) 268 | 269 | 270 | 271 | def sphinx_extension(app, exception): 272 | "Wrapped up as a Sphinx Extension" 273 | 274 | if not app.builder.name in ("html", "dirhtml"): 275 | return 276 | 277 | if not app.config.sphinx_to_github: 278 | if app.config.sphinx_to_github_verbose: 279 | print "Sphinx-to-github: Disabled, doing nothing." 280 | return 281 | 282 | if exception: 283 | if app.config.sphinx_to_github_verbose: 284 | print "Sphinx-to-github: Exception raised in main build, doing nothing." 285 | return 286 | 287 | dir_helper = DirHelper( 288 | os.path.isdir, 289 | os.listdir, 290 | os.walk, 291 | shutil.rmtree 292 | ) 293 | 294 | file_helper = FileSystemHelper( 295 | lambda f, mode: codecs.open(f, mode, app.config.sphinx_to_github_encoding), 296 | os.path.join, 297 | shutil.move, 298 | os.path.exists 299 | ) 300 | 301 | operations_factory = OperationsFactory() 302 | handler_factory = HandlerFactory() 303 | 304 | layout_factory = LayoutFactory( 305 | operations_factory, 306 | handler_factory, 307 | file_helper, 308 | dir_helper, 309 | app.config.sphinx_to_github_verbose, 310 | sys.stdout, 311 | force=True 312 | ) 313 | 314 | layout = layout_factory.create_layout(app.outdir) 315 | layout.process() 316 | 317 | 318 | def setup(app): 319 | "Setup function for Sphinx Extension" 320 | 321 | app.add_config_value("sphinx_to_github", True, '') 322 | app.add_config_value("sphinx_to_github_verbose", True, '') 323 | app.add_config_value("sphinx_to_github_encoding", 'utf-8', '') 324 | 325 | app.connect("build-finished", sphinx_extension) 326 | 327 | 328 | def main(args): 329 | 330 | usage = "usage: %prog [options] " 331 | parser = OptionParser(usage=usage) 332 | parser.add_option("-v","--verbose", action="store_true", 333 | dest="verbose", default=False, help="Provides verbose output") 334 | parser.add_option("-e","--encoding", action="store", 335 | dest="encoding", default="utf-8", help="Encoding for reading and writing files") 336 | opts, args = parser.parse_args(args) 337 | 338 | try: 339 | path = args[0] 340 | except IndexError: 341 | sys.stderr.write( 342 | "Error - Expecting path to html directory:" 343 | "sphinx-to-github \n" 344 | ) 345 | return 346 | 347 | dir_helper = DirHelper( 348 | os.path.isdir, 349 | os.listdir, 350 | os.walk, 351 | shutil.rmtree 352 | ) 353 | 354 | file_helper = FileSystemHelper( 355 | lambda f, mode: codecs.open(f, mode, opts.encoding), 356 | os.path.join, 357 | shutil.move, 358 | os.path.exists 359 | ) 360 | 361 | operations_factory = OperationsFactory() 362 | handler_factory = HandlerFactory() 363 | 364 | layout_factory = LayoutFactory( 365 | operations_factory, 366 | handler_factory, 367 | file_helper, 368 | dir_helper, 369 | opts.verbose, 370 | sys.stdout, 371 | force=False 372 | ) 373 | 374 | layout = layout_factory.create_layout(path) 375 | layout.process() 376 | 377 | 378 | 379 | if __name__ == "__main__": 380 | main(sys.argv[1:]) 381 | 382 | 383 | 384 | -------------------------------------------------------------------------------- /sphinxtogithub/tests/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | class MockExists(object): 3 | 4 | def __call__(self, name): 5 | self.name = name 6 | return True 7 | 8 | class MockRemove(MockExists): 9 | 10 | pass 11 | 12 | 13 | class MockStream(object): 14 | 15 | def __init__(self): 16 | self.msgs = [] 17 | 18 | def write(self, msg): 19 | self.msgs.append(msg) 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /sphinxtogithub/tests/directoryhandler.py: -------------------------------------------------------------------------------- 1 | 2 | import unittest 3 | import os 4 | 5 | import sphinxtogithub 6 | 7 | 8 | class MockRenamer(object): 9 | 10 | def __call__(self, from_, to): 11 | 12 | self.from_ = from_ 13 | self.to = to 14 | 15 | class TestDirectoryHandler(unittest.TestCase): 16 | 17 | def setUp(self): 18 | 19 | self.directory = "_static" 20 | self.new_directory = "static" 21 | self.root = os.path.join("build", "html") 22 | renamer = MockRenamer() 23 | self.dir_handler = sphinxtogithub.DirectoryHandler(self.directory, self.root, renamer) 24 | 25 | def tearDown(self): 26 | 27 | self.dir_handler = None 28 | 29 | 30 | def testPath(self): 31 | 32 | self.assertEqual(self.dir_handler.path(), os.path.join(self.root, self.directory)) 33 | 34 | def testRelativePath(self): 35 | 36 | dir_name = "css" 37 | dir_path = os.path.join(self.root, self.directory, dir_name) 38 | filename = "cssfile.css" 39 | 40 | self.assertEqual( 41 | self.dir_handler.relative_path(dir_path, filename), 42 | os.path.join(self.directory, dir_name, filename) 43 | ) 44 | 45 | def testNewRelativePath(self): 46 | 47 | dir_name = "css" 48 | dir_path = os.path.join(self.root, self.directory, dir_name) 49 | filename = "cssfile.css" 50 | 51 | self.assertEqual( 52 | self.dir_handler.new_relative_path(dir_path, filename), 53 | os.path.join(self.new_directory, dir_name, filename) 54 | ) 55 | 56 | def testProcess(self): 57 | 58 | self.dir_handler.process() 59 | 60 | self.assertEqual( 61 | self.dir_handler.renamer.to, 62 | os.path.join(self.root, self.new_directory) 63 | ) 64 | 65 | self.assertEqual( 66 | self.dir_handler.renamer.from_, 67 | os.path.join(self.root, self.directory) 68 | ) 69 | 70 | 71 | def testSuite(): 72 | suite = unittest.TestSuite() 73 | 74 | suite.addTest(TestDirectoryHandler("testPath")) 75 | suite.addTest(TestDirectoryHandler("testRelativePath")) 76 | suite.addTest(TestDirectoryHandler("testNewRelativePath")) 77 | suite.addTest(TestDirectoryHandler("testProcess")) 78 | 79 | return suite 80 | 81 | -------------------------------------------------------------------------------- /sphinxtogithub/tests/filehandler.py: -------------------------------------------------------------------------------- 1 | 2 | import unittest 3 | 4 | import sphinxtogithub 5 | 6 | class MockFileObject(object): 7 | 8 | before = """ 9 | Breathe's documentation — BreatheExample v0.0.1 documentation 10 | 11 | 12 | """ 13 | 14 | after = """ 15 | Breathe's documentation — BreatheExample v0.0.1 documentation 16 | 17 | 18 | """ 19 | 20 | def read(self): 21 | 22 | return self.before 23 | 24 | def write(self, text): 25 | 26 | self.written = text 27 | 28 | class MockOpener(object): 29 | 30 | def __init__(self): 31 | 32 | self.file_object = MockFileObject() 33 | 34 | def __call__(self, name, readmode="r"): 35 | 36 | self.name = name 37 | 38 | return self.file_object 39 | 40 | 41 | 42 | class TestFileHandler(unittest.TestCase): 43 | 44 | def testProcess(self): 45 | 46 | filepath = "filepath" 47 | 48 | opener = MockOpener() 49 | file_handler = sphinxtogithub.FileHandler(filepath, [], opener) 50 | 51 | file_handler.process() 52 | 53 | self.assertEqual(opener.file_object.written, MockFileObject.before) 54 | self.assertEqual(opener.name, filepath) 55 | 56 | def testProcessWithReplacers(self): 57 | 58 | filepath = "filepath" 59 | 60 | replacers = [] 61 | replacers.append(sphinxtogithub.Replacer("_static/default.css", "static/default.css")) 62 | replacers.append(sphinxtogithub.Replacer("_static/pygments.css", "static/pygments.css")) 63 | 64 | opener = MockOpener() 65 | file_handler = sphinxtogithub.FileHandler(filepath, replacers, opener) 66 | 67 | file_handler.process() 68 | 69 | self.assertEqual(opener.file_object.written, MockFileObject.after) 70 | 71 | 72 | 73 | def testSuite(): 74 | suite = unittest.TestSuite() 75 | 76 | suite.addTest(TestFileHandler("testProcess")) 77 | suite.addTest(TestFileHandler("testProcessWithReplacers")) 78 | 79 | return suite 80 | 81 | -------------------------------------------------------------------------------- /sphinxtogithub/tests/layout.py: -------------------------------------------------------------------------------- 1 | 2 | from sphinxtogithub.tests import MockExists, MockRemove 3 | 4 | import sphinxtogithub 5 | import unittest 6 | 7 | class MockHandler(object): 8 | 9 | def __init__(self): 10 | 11 | self.processed = False 12 | 13 | def process(self): 14 | 15 | self.processed = True 16 | 17 | 18 | 19 | class TestLayout(unittest.TestCase): 20 | 21 | def testProcess(self): 22 | 23 | directory_handlers = [] 24 | file_handlers = [] 25 | 26 | for i in range(0, 10): 27 | directory_handlers.append(MockHandler()) 28 | for i in range(0, 5): 29 | file_handlers.append(MockHandler()) 30 | 31 | layout = sphinxtogithub.Layout(directory_handlers, file_handlers) 32 | 33 | layout.process() 34 | 35 | # Check all handlers are processed by reducing them with "and" 36 | self.assert_(reduce(lambda x, y: x and y.processed, directory_handlers, True)) 37 | self.assert_(reduce(lambda x, y: x and y.processed, file_handlers, True)) 38 | 39 | 40 | def testSuite(): 41 | suite = unittest.TestSuite() 42 | 43 | suite.addTest(TestLayout("testProcess")) 44 | 45 | return suite 46 | 47 | -------------------------------------------------------------------------------- /sphinxtogithub/tests/layoutfactory.py: -------------------------------------------------------------------------------- 1 | 2 | from sphinxtogithub.tests import MockStream 3 | 4 | import sphinxtogithub 5 | import unittest 6 | import os 7 | import shutil 8 | 9 | root = "test_path" 10 | dirs = ["dir1", "dir2", "dir_", "d_ir", "_static", "_source"] 11 | files = ["file1.html", "nothtml.txt", "file2.html", "javascript.js"] 12 | 13 | def mock_is_dir(path): 14 | 15 | directories = [ os.path.join(root, dir_) for dir_ in dirs ] 16 | 17 | return path in directories 18 | 19 | def mock_list_dir(path): 20 | 21 | contents = [] 22 | contents.extend(dirs) 23 | contents.extend(files) 24 | return contents 25 | 26 | def mock_walk(path): 27 | 28 | yield path, dirs, files 29 | 30 | class MockHandlerFactory(object): 31 | 32 | def create_file_handler(self, name, replacers, opener): 33 | 34 | return sphinxtogithub.FileHandler(name, replacers, opener) 35 | 36 | def create_dir_handler(self, name, root, renamer): 37 | 38 | return sphinxtogithub.DirectoryHandler(name, root, renamer) 39 | 40 | 41 | class TestLayoutFactory(unittest.TestCase): 42 | 43 | def setUp(self): 44 | 45 | verbose = True 46 | force = False 47 | stream = MockStream() 48 | dir_helper = sphinxtogithub.DirHelper( 49 | mock_is_dir, 50 | mock_list_dir, 51 | mock_walk, 52 | shutil.rmtree 53 | ) 54 | 55 | file_helper = sphinxtogithub.FileSystemHelper( 56 | open, 57 | os.path.join, 58 | shutil.move, 59 | os.path.exists 60 | ) 61 | 62 | operations_factory = sphinxtogithub.OperationsFactory() 63 | handler_factory = MockHandlerFactory() 64 | 65 | self.layoutfactory = sphinxtogithub.LayoutFactory( 66 | operations_factory, 67 | handler_factory, 68 | file_helper, 69 | dir_helper, 70 | verbose, 71 | stream, 72 | force 73 | ) 74 | 75 | def tearDown(self): 76 | 77 | self.layoutfactory = None 78 | 79 | def testUnderscoreCheck(self): 80 | 81 | func = self.layoutfactory.is_underscore_dir 82 | self.assert_(func(root, "_static")) 83 | self.assert_(not func(root, "dir_")) 84 | self.assert_(not func(root, "d_ir")) 85 | self.assert_(not func(root, "dir1")) 86 | 87 | 88 | def testCreateLayout(self): 89 | 90 | layout = self.layoutfactory.create_layout(root) 91 | 92 | dh = layout.directory_handlers 93 | self.assertEqual(dh[0].name, "_static") 94 | self.assertEqual(dh[1].name, "_source") 95 | self.assertEqual(len(dh), 2) 96 | 97 | fh = layout.file_handlers 98 | self.assertEqual(fh[0].name, os.path.join(root,"file1.html")) 99 | self.assertEqual(fh[1].name, os.path.join(root,"file2.html")) 100 | self.assertEqual(fh[2].name, os.path.join(root,"javascript.js")) 101 | self.assertEqual(len(fh), 3) 102 | 103 | 104 | 105 | 106 | def testSuite(): 107 | suite = unittest.TestSuite() 108 | 109 | suite.addTest(TestLayoutFactory("testUnderscoreCheck")) 110 | suite.addTest(TestLayoutFactory("testCreateLayout")) 111 | 112 | return suite 113 | 114 | -------------------------------------------------------------------------------- /sphinxtogithub/tests/remover.py: -------------------------------------------------------------------------------- 1 | 2 | from sphinxtogithub.tests import MockExists, MockRemove 3 | 4 | import sphinxtogithub 5 | import unittest 6 | 7 | class TestRemover(unittest.TestCase): 8 | 9 | def testCall(self): 10 | 11 | exists = MockExists() 12 | remove = MockRemove() 13 | remover = sphinxtogithub.Remover(exists, remove) 14 | 15 | filepath = "filepath" 16 | remover(filepath) 17 | 18 | self.assertEqual(filepath, exists.name) 19 | self.assertEqual(filepath, remove.name) 20 | 21 | 22 | def testSuite(): 23 | suite = unittest.TestSuite() 24 | 25 | suite.addTest(TestRemover("testCall")) 26 | 27 | return suite 28 | 29 | -------------------------------------------------------------------------------- /sphinxtogithub/tests/renamer.py: -------------------------------------------------------------------------------- 1 | 2 | from sphinxtogithub.tests import MockExists, MockRemove, MockStream 3 | 4 | import sphinxtogithub 5 | import unittest 6 | import os 7 | 8 | 9 | 10 | class MockRename(object): 11 | 12 | def __call__(self, from_, to): 13 | self.from_ = from_ 14 | self.to = to 15 | 16 | class TestForceRename(unittest.TestCase): 17 | 18 | def testCall(self): 19 | 20 | rename = MockRename() 21 | remove = MockRemove() 22 | renamer = sphinxtogithub.ForceRename(rename, remove) 23 | 24 | from_ = "from" 25 | to = "to" 26 | renamer(from_, to) 27 | 28 | self.assertEqual(rename.from_, from_) 29 | self.assertEqual(rename.to, to) 30 | self.assertEqual(remove.name, to) 31 | 32 | 33 | class TestVerboseRename(unittest.TestCase): 34 | 35 | def testCall(self): 36 | 37 | rename = MockRename() 38 | stream = MockStream() 39 | renamer = sphinxtogithub.VerboseRename(rename, stream) 40 | 41 | from_ = os.path.join("path", "to", "from") 42 | to = os.path.join("path", "to", "to") 43 | renamer(from_, to) 44 | 45 | self.assertEqual(rename.from_, from_) 46 | self.assertEqual(rename.to, to) 47 | self.assertEqual( 48 | stream.msgs[0], 49 | "Renaming directory '%s' -> '%s'\n" % (os.path.basename(from_), os.path.basename(to)) 50 | ) 51 | 52 | 53 | 54 | def testSuite(): 55 | suite = unittest.TestSuite() 56 | 57 | suite.addTest(TestForceRename("testCall")) 58 | suite.addTest(TestVerboseRename("testCall")) 59 | 60 | return suite 61 | 62 | -------------------------------------------------------------------------------- /sphinxtogithub/tests/replacer.py: -------------------------------------------------------------------------------- 1 | 2 | import unittest 3 | 4 | import sphinxtogithub 5 | 6 | class TestReplacer(unittest.TestCase): 7 | 8 | before = """ 9 | Breathe's documentation — BreatheExample v0.0.1 documentation 10 | 11 | 12 | """ 13 | 14 | after = """ 15 | Breathe's documentation — BreatheExample v0.0.1 documentation 16 | 17 | 18 | """ 19 | 20 | def testReplace(self): 21 | 22 | replacer = sphinxtogithub.Replacer("_static/default.css", "static/default.css") 23 | self.assertEqual(replacer.process(self.before), self.after) 24 | 25 | 26 | def testSuite(): 27 | 28 | suite = unittest.TestSuite() 29 | 30 | suite.addTest(TestReplacer("testReplace")) 31 | 32 | return suite 33 | 34 | 35 | -------------------------------------------------------------------------------- /sphinxtogithub/tests/setup.py: -------------------------------------------------------------------------------- 1 | 2 | import sphinxtogithub 3 | import unittest 4 | 5 | class MockApp(object): 6 | 7 | def __init__(self): 8 | self.config_values = {} 9 | self.connections = {} 10 | 11 | def add_config_value(self, name, default, rebuild): 12 | 13 | self.config_values[name] = (default, rebuild) 14 | 15 | def connect(self, stage, function): 16 | 17 | self.connections[stage] = function 18 | 19 | 20 | class TestSetup(unittest.TestCase): 21 | 22 | def testSetup(self): 23 | 24 | # Sadly not flexible enough to test it independently 25 | # so the tests rely on and test the values pass in the 26 | # production code 27 | app = MockApp() 28 | sphinxtogithub.setup(app) 29 | 30 | self.assertEqual(app.connections["build-finished"], sphinxtogithub.sphinx_extension) 31 | self.assertEqual(len(app.connections), 1) 32 | 33 | self.assertEqual(app.config_values["sphinx_to_github"],(True, '')) 34 | self.assertEqual(app.config_values["sphinx_to_github_verbose"],(True, '')) 35 | self.assertEqual(app.config_values["sphinx_to_github_encoding"],('utf-8', '')) 36 | self.assertEqual(len(app.config_values),3) 37 | 38 | 39 | def testSuite(): 40 | suite = unittest.TestSuite() 41 | 42 | suite.addTest(TestSetup("testSetup")) 43 | 44 | return suite 45 | 46 | --------------------------------------------------------------------------------