diff --git a/.travis.yml b/.travis.yml index 4e921986..c52c2474 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,7 +21,7 @@ install: - conda config --add channels conda-forge # For sphinxcontrib.autoprogram - conda update -q conda - conda info -a - - conda create -q -n test-environment python=$TRAVIS_PYTHON_VERSION requests cryptography sphinx pyflakes sphinxcontrib-autoprogram pytest sphinx-issues + - conda create -q -n test-environment python=$TRAVIS_PYTHON_VERSION requests cryptography sphinx pyflakes sphinxcontrib-autoprogram pytest sphinx-issues pyyaml - source activate test-environment script: @@ -31,12 +31,17 @@ script: cd docs; make html; cd ..; - python -m doctr deploy --key-path deploy_key.enc .; - python -m doctr deploy --key-path deploy_key.enc --gh-pages-docs docs; - python -m doctr deploy --no-require-master --built-docs docs/_build/html --key-path deploy_key.enc "docs-$TRAVIS_BRANCH"; - python -m doctr deploy --no-require-master --key-path deploy_key.enc --no-sync --command "echo test" docs; + python -m doctr deploy --sync .; + python -m doctr deploy --sync --gh-pages-docs docs; + python -m doctr deploy --sync --no-require-master --built-docs docs/_build/html "docs-$TRAVIS_BRANCH"; + python -m doctr deploy --no-require-master --command "echo test" docs; fi - if [[ "${TESTS}" == "true" ]]; then pyflakes doctr; py.test doctr; fi +doctr: + key-path: deploy_key.enc + require-master: true + sync: False + lubalubadubdub: False diff --git a/docs/changelog.rst b/docs/changelog.rst index 526b58a1..4f41c8c9 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -7,7 +7,13 @@ Current - The ``--gh-pages-docs`` flag of ``doctr deploy`` has been deprecated. Specify the deploy directory like ``doctr deploy .`` or ``doctr deploy docs``. There is also no longer a default deploy directory. (:issue:`128`) - +- ``setup_GitHub_push`` now takes a ``branch_whitelist`` parameter instead of + of a ``require_master`` +- ``.travis.yml`` can be used to store some of doctr configuration in addition + to the command line flags. Write doctr configuration under the ``doctr`` key. + :issue:`137` +- All boolean command line flags now have a their counterpart that can overwrite + the config values set in ``.travis.yml`` 1.4.1 (2017-01-11) ================== diff --git a/docs/commandline.rst b/docs/commandline.rst index facf703c..58cef768 100644 --- a/docs/commandline.rst +++ b/docs/commandline.rst @@ -4,3 +4,42 @@ .. autoprogram:: doctr.__main__:get_parser() :prog: doctr + + +Configuration +------------- + +In addition to command line arguments you can configure ``doctr`` using the +``.travis.yml`` files. Command line arguments take precedence over the value +present in the configuration file. + +The configuration parameters available from the ``.travis.yml`` file mirror +their command line siblings except doubledashes ``--`` and ``--no-`` prefix are +dropped. + +Use a ``doctr`` section in your ``travis.yml`` file to store your doctr +configuration: + +.. code:: yaml + + - language: python + - script: + - set -e + - py.test + - cd docs + - make html + - cd .. + - doctr deploy . + - doctr: + - key-path : 'path/to/key/from/repo/root/path.key' + - deploy-repo : 'myorg/myrepo' + + +The following options are available from the configuration file and not from +the command line: + +``branches``: + A list of regular expression that matches branches on which ``doctr`` should + still deploy the documentation. For example ``.*\.x`` will deploy all + branches like ``3.x``, ``4.x`` ... + diff --git a/doctr/__main__.py b/doctr/__main__.py index 6a9a73bf..bf4f7c71 100644 --- a/doctr/__main__.py +++ b/doctr/__main__.py @@ -23,9 +23,14 @@ import sys import os +import os.path import argparse import shlex import subprocess +import yaml +import json + +from pathlib import Path from textwrap import dedent @@ -35,7 +40,69 @@ get_current_repo, sync_from_log, find_sphinx_build_dir, run) from . import __version__ -def get_parser(): +def make_parser_with_config_adder(parser, config): + """factory function for a smarter parser: + + return an utility function that pull default from the config as well. + + Pull the default for parser not only from the ``default`` kwarg, + but also if an identical value is find in ``config`` where leading + ``--`` or ``--no`` is removed. + + If the option is a boolean flag, automatically register an opposite, + exclusive option by prepending or removing the `--no-`. This is useful + to overwrite config in ``.travis.yml`` + + Mutate the config object and remove know keys in order to detect unused + options afterwoard. + """ + + def internal(arg, **kwargs): + invert = { + 'store_true':'store_false', + 'store_false':'store_true', + } + if arg.startswith('--no-'): + key = arg[5:] + else: + key = arg[2:] + if 'default' in kwargs: + if key in config: + kwargs['default'] = config[key] + del config[key] + action = kwargs.get('action') + if action in invert: + exclusive_grp = parser.add_mutually_exclusive_group() + exclusive_grp.add_argument(arg, **kwargs) + kwargs['action'] = invert[action] + kwargs['help'] = 'Inverse of "%s"' % arg + if arg.startswith('--no-'): + arg = '--%s' % arg[5:] + else: + arg = '--no-%s' % arg[2:] + exclusive_grp.add_argument(arg, **kwargs) + else: + parser.add_argument(arg, **kwargs) + + return internal + + +def get_parser(config=None): + """ + return a parser suitable to parse CL arguments. + + Parameters + ---------- + + config: dict + Default values to fall back on, if not given. + + Returns + ------- + + An argparse parser configured to parse the command lines arguments of + sys.argv which will default on values present in ``config``. + """ # This uses RawTextHelpFormatter so that the description (the docstring of # this module) is formatted correctly. Unfortunately, that means that # parser help is not text wrapped (but all other help is). @@ -45,47 +112,55 @@ def get_parser(): options available. """, ) + + if not config: + config={} parser.add_argument('-V', '--version', action='version', version='doctr ' + __version__) subcommand = parser.add_subparsers(title='subcommand', dest='subcommand') + deploy_parser = subcommand.add_parser('deploy', help="""Deploy the docs to GitHub from Travis.""") deploy_parser.set_defaults(func=deploy) - deploy_parser.add_argument('deploy_directory', type=str, nargs='?', - help="""Directory to deploy the html documentation to on gh-pages.""") - deploy_parser.add_argument('--force', action='store_true', help="""Run the deploy command even + deploy_parser_add_argument = make_parser_with_config_adder(deploy_parser, config) + deploy_parser_add_argument('--force', action='store_true', help="""Run the deploy command even if we do not appear to be on Travis.""") - deploy_parser.add_argument('--token', action='store_true', default=False, + deploy_parser_add_argument('deploy_directory', type=str, nargs='?', + help="""Directory to deploy the html documentation to on gh-pages.""") + + deploy_parser_add_argument('--token', action='store_true', default=False, help="""Push to GitHub using a personal access token. Use this if you used 'doctr configure --token'.""") - deploy_parser.add_argument('--key-path', default='github_deploy_key.enc', + deploy_parser_add_argument('--key-path', default='github_deploy_key.enc', help="""Path of the encrypted GitHub deploy key. The default is %(default)r.""") - deploy_parser.add_argument('--built-docs', default=None, + deploy_parser_add_argument('--built-docs', default=None, help="""Location of the built html documentation to be deployed to gh-pages. If not specified, Doctr will try to automatically detect build location""") - deploy_parser.add_argument('--tmp-dir', default=None, + deploy_parser_add_argument('--tmp-dir', default=None, help=argparse.SUPPRESS) - deploy_parser.add_argument('--deploy-repo', default=None, help="""Repo to + deploy_parser_add_argument('--deploy-repo', default=None, help="""Repo to deploy the docs to. By default, it deploys to the repo Doctr is run from.""") - deploy_parser.add_argument('--no-require-master', dest='require_master', action='store_false', + deploy_parser_add_argument('--no-require-master', dest='require_master', action='store_false', default=True, help="""Allow docs to be pushed from a branch other than master""") - deploy_parser.add_argument('--command', default=None, help="""Command to + deploy_parser_add_argument('--command', default=None, help="""Command to be run before committing and pushing. If the command creates additional files that should be deployed, they should be added to the index.""") - deploy_parser.add_argument('--no-sync', dest='sync', action='store_false', + deploy_parser_add_argument('--no-sync', dest='sync', action='store_false', default=True, help="""Don't sync any files. This is generally used in conjunction with the --command flag, for instance, if the command syncs the files for you. Any files you wish to commit should be added to the index.""") - deploy_parser.add_argument('--no-push', dest='push', action='store_false', - default=True, help="Run all the steps except the last push step." + deploy_parser_add_argument('--no-push', dest='push', action='store_false', + default=True, help="Run all the steps except the last push step. " "Useful for debugging") - deploy_parser.add_argument('--gh-pages-docs', default=None, + deploy_parser_add_argument('--gh-pages-docs', default=None, help="""!!DEPRECATED!! Directory to deploy the html documentation to on gh-pages. The default is %(default)r. The deploy directory should be passed as the first argument to 'doctr deploy'. This flag is kept for backwards compatibility.""") + if config: + print('Warning, The following options in `.travis.yml` were not recognised:\n%s' % json.dumps(config, indent=2)) configure_parser = subcommand.add_parser('configure', help="Configure doctr. This command should be run locally (not on Travis).") configure_parser.set_defaults(func=configure) @@ -106,6 +181,23 @@ def get_parser(): return parser +def get_config(): + """ + This load some configuration from the ``.travis.yml``, if file is present, + ``doctr`` key if present. + """ + p = Path('.travis.yml') + if not p.exists(): + return {} + with p.open() as f: + travis_config = yaml.safe_load(f.read()) + + config = travis_config.get('doctr', {}) + + if not isinstance(config, dict): + raise ValueError('config is not a dict: {}'.format(config)) + return config + def process_args(parser): args = parser.parse_args() @@ -126,6 +218,8 @@ def deploy(args, parser): parser.error("doctr does not appear to be running on Travis. Use " "doctr deploy --force to run anyway.") + config = get_config() + if args.tmp_dir: parser.error("The --tmp-dir flag has been removed (doctr no longer uses a temporary directory when deploying).") @@ -145,9 +239,13 @@ def deploy(args, parser): current_commit = subprocess.check_output(['git', 'rev-parse', 'HEAD']).decode('utf-8').strip() try: + + branch_whitelist = {'master'} if args.require_master else set({}) + branch_whitelist.update(set(config.get('branches',set({})))) + can_push = setup_GitHub_push(deploy_repo, auth_type='token' if args.token else 'deploy_key', full_key_path=args.key_path, - require_master=args.require_master) + branch_whitelist=branch_whitelist) if args.sync: built_docs = args.built_docs or find_sphinx_build_dir() @@ -284,7 +382,8 @@ def configure(args, parser): """.format(encrypted_variable=encrypted_variable.decode('utf-8'), N=N))) def main(): - return process_args(get_parser()) + config = get_config() + return process_args(get_parser(config=config)) if __name__ == '__main__': sys.exit(main()) diff --git a/doctr/travis.py b/doctr/travis.py index 3011199a..6ef9dcca 100644 --- a/doctr/travis.py +++ b/doctr/travis.py @@ -8,6 +8,7 @@ import subprocess import sys import glob +import re from cryptography.fernet import Fernet @@ -132,7 +133,7 @@ def get_current_repo(): _, org, git_repo = remote_url.rsplit('.git', 1)[0].rsplit('/', 2) return (org + '/' + git_repo) -def setup_GitHub_push(deploy_repo, auth_type='deploy_key', full_key_path='github_deploy_key.enc', require_master=True): +def setup_GitHub_push(deploy_repo, auth_type='deploy_key', full_key_path='github_deploy_key.enc', require_master=None, branch_whitelist=None): """ Setup the remote to push to GitHub (to be run on Travis). @@ -144,6 +145,17 @@ def setup_GitHub_push(deploy_repo, auth_type='deploy_key', full_key_path='github For ``auth_type='deploy_key'``, this sets up the remote with ssh access. """ + + if branch_whitelist is None: + branch_whitelist={'master'} + + if require_master is not None: + import warnings + warnings.warn("`setup_GitHub_push`'s `require_master` argument in favor of `branch_whitelist=['master']`", + DeprecationWarning, + stacklevel=2) + branch_whitelist.add('master') + canpush = True if auth_type not in ['deploy_key', 'token']: raise ValueError("auth_type must be 'deploy_key' or 'token'") @@ -151,7 +163,7 @@ def setup_GitHub_push(deploy_repo, auth_type='deploy_key', full_key_path='github TRAVIS_BRANCH = os.environ.get("TRAVIS_BRANCH", "") TRAVIS_PULL_REQUEST = os.environ.get("TRAVIS_PULL_REQUEST", "") - if TRAVIS_BRANCH != "master" and require_master: + if any([re.compile(x).match(TRAVIS_BRANCH) for x in branch_whitelist]): print("The docs are only pushed to gh-pages from master. To allow pushing from " "a non-master branch, use the --no-require-master flag", file=sys.stderr) print("This is the {TRAVIS_BRANCH} branch".format(TRAVIS_BRANCH=TRAVIS_BRANCH), file=sys.stderr) diff --git a/setup.py b/setup.py index 7ec8b44e..5d7ae1ec 100644 --- a/setup.py +++ b/setup.py @@ -20,6 +20,7 @@ entry_points={'console_scripts': [ 'doctr = doctr.__main__:main']}, python_requires= '>=3.5', install_requires=[ + 'pyyaml', 'requests', 'cryptography', ],