def test_empty(tmpdir, caplog, error): """With no settings defined. :param tmpdir: pytest fixture. :param caplog: pytest extension fixture. :param bool error: Malformed conf.py. """ tmpdir.ensure('contents.rst') local_conf = tmpdir.join('conf.py') if error: local_conf.write('undefined') else: local_conf.write('project = "MyProject"') # Run. config = read_local_conf(str(local_conf)) records = [(r.levelname, r.message) for r in caplog.records] # Verify. if error: assert records[-1] == ( 'WARNING', 'Unable to read file, continuing with only CLI args.') else: assert [r[0] for r in records] == ['INFO', 'DEBUG'] assert config == dict()
def test_settings(tmpdir): """Test with settings in conf.py. :param tmpdir: pytest fixture. """ tmpdir.ensure('index.rst') local_conf = tmpdir.join('conf.py') local_conf.write('import re\n\n' 'master_doc = "index"\n' 'project = "MyProject"\n' 'scv__already_set = {"one", "two"}\n' 'scv_already_set = {"three", "four"}\n' 'scv_root_ref = "feature"\n' 'scv_unknown_item = True\n') # Run. config = read_local_conf(str(local_conf)) # Verify. assert config == dict(root_ref='feature')
def test_settings(tmpdir): """Test with settings in conf.py. :param tmpdir: pytest fixture. """ tmpdir.ensure('index.rst') local_conf = tmpdir.join('conf.py') local_conf.write( 'import re\n\n' 'master_doc = "index"\n' 'project = "MyProject"\n' 'scv__already_set = {"one", "two"}\n' 'scv_already_set = {"three", "four"}\n' 'scv_root_ref = "feature"\n' 'scv_unknown_item = True\n' ) # Run. config = read_local_conf(str(local_conf)) # Verify. assert config == dict(root_ref='feature')
def test_empty(tmpdir, caplog, error): """With no settings defined. :param tmpdir: pytest fixture. :param caplog: pytest extension fixture. :param bool error: Malformed conf.py. """ tmpdir.ensure('contents.rst') local_conf = tmpdir.join('conf.py') if error: local_conf.write('undefined') else: local_conf.write('project = "MyProject"') # Run. config = read_local_conf(str(local_conf)) records = [(r.levelname, r.message) for r in caplog.records] # Verify. if error: assert records[-1] == ('WARNING', 'Unable to read file, continuing with only CLI args.') else: assert [r[0] for r in records] == ['INFO', 'DEBUG'] assert config == dict()
def build(config, rel_source, destination, **options): """Fetch branches/tags and build all locally. Just fetch all remote branches and tags, export them to a temporary directory, run sphinx-build on each one, and then store all built documentation in DESTINATION. REL_SOURCE is the path to the docs directory relative to the git root. If the source directory has moved around between git tags you can specify additional directories. DESTINATION is the path to the local directory that will hold all generated docs for all versions. To pass options to sphinx-build (run for every branch/tag) use a double hyphen (e.g. build docs docs/_build/html -- -D setting=value). \f :param sphinxcontrib.versioning.lib.Config config: Runtime configuration. :param tuple rel_source: Possible relative paths (to git root) of Sphinx directory containing conf.py (e.g. docs). :param str destination: Destination directory to copy/overwrite built docs to. Does not delete old files. :param dict options: Additional Click options. """ if 'pre' in config: config.pop('pre')(rel_source) config.update({k: v for k, v in options.items() if v}) if config.local_conf: config.update(read_local_conf(config.local_conf), ignore_set=True) if NO_EXECUTE: raise RuntimeError(config, rel_source, destination) log = logging.getLogger(__name__) # Gather git data. log.info('Gathering info about the remote git repository...') conf_rel_paths = [os.path.join(s, 'conf.py') for s in rel_source] remotes = gather_git_info(config.git_root, conf_rel_paths, config.whitelist_branches, config.whitelist_tags) if not remotes: log.error('No docs found in any remote branch/tag. Nothing to do.') raise HandledError versions = Versions( remotes, sort=config.sort, priority=config.priority, invert=config.invert, pdf_file=config.pdf_file, ) # Get root ref. if not override_root_main_ref(config, versions.remotes, False): log.error('Root ref %s not found in: %s', config.root_ref, ' '.join(r[1] for r in remotes)) raise HandledError log.info('Root ref is: %s', config.root_ref) # Get banner main ref. if not config.show_banner: config.update(dict(banner_greatest_tag=False, banner_main_ref=None, banner_recent_tag=False), overwrite=True) elif not override_root_main_ref(config, versions.remotes, True): log.warning('Banner main ref %s not found in: %s', config.banner_main_ref, ' '.join(r[1] for r in remotes)) log.warning('Disabling banner.') config.update(dict(banner_greatest_tag=False, banner_main_ref=None, banner_recent_tag=False, show_banner=False), overwrite=True) else: log.info('Banner main ref is: %s', config.banner_main_ref) # Pre-build. log.info( "Pre-running Sphinx to collect versions' master_doc and other info.") exported_root = pre_build(config.git_root, versions) if config.banner_main_ref and config.banner_main_ref not in [ r['name'] for r in versions.remotes ]: log.warning( 'Banner main ref %s failed during pre-run. Disabling banner.', config.banner_main_ref) config.update(dict(banner_greatest_tag=False, banner_main_ref=None, banner_recent_tag=False, show_banner=False), overwrite=True) # Build. build_all(exported_root, destination, versions) # Cleanup. log.debug('Removing: %s', exported_root) shutil.rmtree(exported_root) # Store versions in state for push(). config['versions'] = versions
def push(ctx, config, rel_source, dest_branch, rel_dest, **options): """Build locally and then push to remote branch. First the build sub command is invoked which takes care of building all versions of your documentation in a temporary directory. If that succeeds then all built documents will be pushed to a remote branch. REL_SOURCE is the path to the docs directory relative to the git root. If the source directory has moved around between git tags you can specify additional directories. DEST_BRANCH is the branch name where generated docs will be committed to. The branch will then be pushed to remote. If there is a race condition with another job pushing to remote the docs will be re-generated and pushed again. REL_DEST is the path to the directory that will hold all generated docs for all versions relative to the git roof of DEST_BRANCH. To pass options to sphinx-build (run for every branch/tag) use a double hyphen (e.g. push docs gh-pages . -- -D setting=value). \f :param click.core.Context ctx: Click context. :param sphinxcontrib.versioning.lib.Config config: Runtime configuration. :param tuple rel_source: Possible relative paths (to git root) of Sphinx directory containing conf.py (e.g. docs). :param str dest_branch: Branch to clone and push to. :param str rel_dest: Relative path (to git root) to write generated docs to. :param dict options: Additional Click options. """ if 'pre' in config: config.pop('pre')(rel_source) config.update({k: v for k, v in options.items() if v}) if config.local_conf: config.update(read_local_conf(config.local_conf), ignore_set=True) if NO_EXECUTE: raise RuntimeError(config, rel_source, dest_branch, rel_dest) log = logging.getLogger(__name__) # Clone, build, push. for _ in range(PUSH_RETRIES): with TempDir() as temp_dir: log.info('Cloning %s into temporary directory...', dest_branch) try: clone(config.git_root, temp_dir, config.push_remote, dest_branch, rel_dest, config.grm_exclude) except GitError as exc: log.error(exc.message) log.error(exc.output) raise HandledError log.info('Building docs...') ctx.invoke(build, rel_source=rel_source, destination=os.path.join(temp_dir, rel_dest)) versions = config.pop('versions') log.info('Attempting to push to branch %s on remote repository.', dest_branch) try: if commit_and_push(temp_dir, config.push_remote, versions): return except GitError as exc: log.error(exc.message) log.error(exc.output) raise HandledError log.warning('Failed to push to remote repository. Retrying in %d seconds...', PUSH_SLEEP) time.sleep(PUSH_SLEEP) # Failed if this is reached. log.error('Ran out of retries, giving up.') raise HandledError
def build(config, rel_source, destination, **options): """Fetch branches/tags and build all locally. Doesn't push anything to remote. Just fetch all remote branches and tags, export them to a temporary directory, run sphinx-build on each one, and then store all built documentation in DESTINATION. REL_SOURCE is the path to the docs directory relative to the git root. If the source directory has moved around between git tags you can specify additional directories. DESTINATION is the path to the local directory that will hold all generated docs for all versions. To pass options to sphinx-build (run for every branch/tag) use a double hyphen (e.g. build docs docs/_build/html -- -D setting=value). \f :param sphinxcontrib.versioning.lib.Config config: Runtime configuration. :param tuple rel_source: Possible relative paths (to git root) of Sphinx directory containing conf.py (e.g. docs). :param str destination: Destination directory to copy/overwrite built docs to. Does not delete old files. :param dict options: Additional Click options. """ if 'pre' in config: config.pop('pre')(rel_source) config.update({k: v for k, v in options.items() if v}) if config.local_conf: config.update(read_local_conf(config.local_conf), ignore_set=True) if NO_EXECUTE: raise RuntimeError(config, rel_source, destination) log = logging.getLogger(__name__) # Gather git data. log.info('Gathering info about the remote git repository...') conf_rel_paths = [os.path.join(s, 'conf.py') for s in rel_source] remotes = gather_git_info(config.git_root, conf_rel_paths, config.whitelist_branches, config.whitelist_tags) if not remotes: log.error('No docs found in any remote branch/tag. Nothing to do.') raise HandledError versions = Versions( remotes, sort=config.sort, priority=config.priority, invert=config.invert, ) # Get root ref. if not override_root_main_ref(config, versions.remotes, False): log.error('Root ref %s not found in: %s', config.root_ref, ' '.join(r[1] for r in remotes)) raise HandledError log.info('Root ref is: %s', config.root_ref) # Get banner main ref. if not config.show_banner: config.update(dict(banner_greatest_tag=False, banner_main_ref=None, banner_recent_tag=False), overwrite=True) elif not override_root_main_ref(config, versions.remotes, True): log.warning('Banner main ref %s not found in: %s', config.banner_main_ref, ' '.join(r[1] for r in remotes)) log.warning('Disabling banner.') config.update(dict(banner_greatest_tag=False, banner_main_ref=None, banner_recent_tag=False, show_banner=False), overwrite=True) else: log.info('Banner main ref is: %s', config.banner_main_ref) # Pre-build. log.info("Pre-running Sphinx to collect versions' master_doc and other info.") exported_root = pre_build(config.git_root, versions) if config.banner_main_ref and config.banner_main_ref not in [r['name'] for r in versions.remotes]: log.warning('Banner main ref %s failed during pre-run. Disabling banner.', config.banner_main_ref) config.update(dict(banner_greatest_tag=False, banner_main_ref=None, banner_recent_tag=False, show_banner=False), overwrite=True) # Build. build_all(exported_root, destination, versions) # Cleanup. log.debug('Removing: %s', exported_root) shutil.rmtree(exported_root) # Store versions in state for push(). config['versions'] = versions
def push(ctx, config, rel_source, dest_branch, rel_dest, **options): """Build locally and then push to remote branch. First the build sub command is invoked which takes care of building all versions of your documentation in a temporary directory. If that succeeds then all built documents will be pushed to a remote branch. REL_SOURCE is the path to the docs directory relative to the git root. If the source directory has moved around between git tags you can specify additional directories. DEST_BRANCH is the branch name where generated docs will be committed to. The branch will then be pushed to remote. If there is a race condition with another job pushing to remote the docs will be re-generated and pushed again. REL_DEST is the path to the directory that will hold all generated docs for all versions relative to the git roof of DEST_BRANCH. To pass options to sphinx-build (run for every branch/tag) use a double hyphen (e.g. push docs gh-pages . -- -D setting=value). \f :param click.core.Context ctx: Click context. :param sphinxcontrib.versioning.lib.Config config: Runtime configuration. :param tuple rel_source: Possible relative paths (to git root) of Sphinx directory containing conf.py (e.g. docs). :param str dest_branch: Branch to clone and push to. :param str rel_dest: Relative path (to git root) to write generated docs to. :param dict options: Additional Click options. """ if 'pre' in config: config.pop('pre')(rel_source) config.update({k: v for k, v in options.items() if v}) if config.local_conf: config.update(read_local_conf(config.local_conf), ignore_set=True) if NO_EXECUTE: raise RuntimeError(config, rel_source, dest_branch, rel_dest) log = logging.getLogger(__name__) # Clone, build, push. for _ in range(PUSH_RETRIES): with TempDir() as temp_dir: log.info('Cloning %s into temporary directory...', dest_branch) try: clone(config.git_root, temp_dir, config.push_remote, dest_branch, rel_dest, config.grm_exclude) except GitError as exc: log.error(exc.message) log.error(exc.output) raise HandledError log.info('Building docs...') ctx.invoke(build, rel_source=rel_source, destination=os.path.join(temp_dir, rel_dest)) versions = config.pop('versions') log.info('Attempting to push to branch %s on remote repository.', dest_branch) try: if commit_and_push(temp_dir, config.push_remote, versions): return except GitError as exc: log.error(exc.message) log.error(exc.output) raise HandledError log.warning( 'Failed to push to remote repository. Retrying in %d seconds...', PUSH_SLEEP) time.sleep(PUSH_SLEEP) # Failed if this is reached. log.error('Ran out of retries, giving up.') raise HandledError