diff --git a/.bandit b/.bandit index 75d550c321..68dc7dd09b 100644 --- a/.bandit +++ b/.bandit @@ -1 +1 @@ -skips: ['B101'] +skips: ['B101', 'B322'] diff --git a/.eslintignore b/.eslintignore index 5e13985e92..4f82c07f6c 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,6 +1,6 @@ doc/* -lib/html/static/js/bootstrap* -lib/html/static/js/dataTables.bootstrap* -lib/html/static/js/jquery* -lib/html/static/js/livestamp.min.js -lib/html/static/js/moment.min.js +metomi/rosie/lib/html/static/js/bootstrap* +metomi/rosie/lib/html/static/js/dataTables.bootstrap* +metomi/rosie/lib/html/static/js/jquery* +metomi/rosie/lib/html/static/js/livestamp.min.js +metomi/rosie/lib/html/static/js/moment.min.js diff --git a/.gitignore b/.gitignore index 707f1eb2c8..877c424b04 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,8 @@ etc/opt lib/bash/rose_init_site doc venv +metomi_rose.egg-info +dist # coverage .coverage diff --git a/.travis.yml b/.travis.yml index e18fecfeaf..b31174eeef 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,7 +20,7 @@ jobs: include: - name: "Unit Tests" install: - - now install coverage cylc linters pytest rose + - now install coverage linters pytest cylc rose script: - now test style - now test units @@ -28,12 +28,14 @@ jobs: - name: "Test Battery" before_install: - export PATH="$PWD/.travis:$PATH" - - now install coverage cylc fcm rose rosie tut_suite + - now install coverage fcm tut_suite + install: + - now install isodatetime cylc rose script: - now test battery + - name: "Documentation" install: - - now install coverage rose sphinx tut_suite + - now install coverage isodatetime rose sphinx tut_suite script: - now test docs - - now build docs diff --git a/.travis/cover.py b/.travis/cover.py index 561a868d60..ee79bcbf4e 100644 --- a/.travis/cover.py +++ b/.travis/cover.py @@ -24,7 +24,7 @@ def main(): - command = ['rose', 'test-battery', '-j', '5'] + command = ['etc/bin/rose-test-battery', '-j', '5'] if call(command + ['--state=save']): # Non-zero return code sys.stderr.write('\n\nRerunning Failed Tests...\n\n') diff --git a/.travis/now b/.travis/now index b99d4daffe..e359c42a10 100755 --- a/.travis/now +++ b/.travis/now @@ -49,7 +49,7 @@ WANDISCO=false _build_docs () { - rose make-docs --strict clean html slides latexpdf + etc/bin/rose-make-docs --strict clean html slides latexpdf } _gh_extract () { # extract project from GitHub to $HOME @@ -79,12 +79,24 @@ _install_coverage () { PY_PATH+=("${TRAVIS_BUILD_DIR}/.travis") } +_install_rose () { + pip install -e . + PY_PATH+=("${TRAVIS_BUILD_DIR}/metomi") + RC_PATH+=("${TRAVIS_BUILD_DIR}/bin") +} + _install_cylc () { APT+=(at heirloom-mailx) GIT+=('cylc|cylc-flow|master') RC_PATH+=("${HOME}/cylc-master/bin") } +_install_isodatetime () { + wget 'https://github.com/metomi/isodatetime/archive/master.tar.gz' \ + -O - | tar -xz -C "${HOME}" + pip install -e "${HOME}/isodatetime-master" +} + _install_fcm () { APT+=(subversion build-essential gfortran libxml-parser-perl \ libconfig-inifiles-perl libdbi-perl libdbd-sqlite3-perl) @@ -98,12 +110,6 @@ _install_pytest () { PIP+=(pytest) } -_install_rose () { - PIP+=(aiofiles) - PY_PATH+=("${TRAVIS_BUILD_DIR}/lib/python/") - RC_PATH+=("${TRAVIS_BUILD_DIR}/bin") -} - _install_rosie () { PIP+=(requests tornado sqlalchemy) RI_PATH+=("$(_path_for python)" "$(_path_for rose)") @@ -140,7 +146,7 @@ _path_for () { } _test_units () { - pytest --cov-append lib/python/rose/tests/* + pytest --cov-append metomi/rose/tests/* } _test_style () { @@ -152,11 +158,10 @@ _test_style () { _test_battery () { cp "${TRAVIS_BUILD_DIR}/.travis/sitecustomize.py" ./lib/python coverage run .travis/cover.py - rm ./lib/python/sitecustomize.py } _test_docs () { - rose make-docs --strict clean linkcheck doctest + etc/bin/rose-make-docs --strict clean linkcheck doctest } _wandisco_configure () { # extract Wandisco stuff diff --git a/.travis/shellchecker b/.travis/shellchecker index 92cc78631b..00ca5178e6 100755 --- a/.travis/shellchecker +++ b/.travis/shellchecker @@ -79,7 +79,7 @@ default () { main . \ --exclude etc/rose-bash-completion \ --exclude t \ - -- -e SC1090 + -- -e SC1090 -e SC2119 # run a lenient check on all test scripts main t -- -S error -e SC1090 diff --git a/README.md b/README.md index b60ae21a82..1ee2188504 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ release for production use. [Installation](http://metomi.github.io/rose/doc/html/installation.html) | [User Guide](http://metomi.github.io/rose/) | -[How to Contribute](CONTRIBUTING.md) +[How to Contribute](https://github.com/metomi/rose/blob/master/CONTRIBUTING.md) ## Copyright and Terms of Use @@ -46,4 +46,4 @@ See . ## Acknowledgement for non-Rose Work -See [Acknowledgement for Non-Rose Work](ACKNOWLEDGEMENT.md). +See [Acknowledgement for Non-Rose Work](https://github.com/metomi/rose/blob/master/ACKNOWLEDGEMENT.md). diff --git a/bin/rose b/bin/rose index 2598f8aef0..da266b0ea2 100755 --- a/bin/rose +++ b/bin/rose @@ -37,7 +37,9 @@ if ${ROSE_DEBUG:-false}; then set -x fi -. "$(dirname "$0")/../lib/bash/rose_init" +# shellcheck source=lib/bash/rose_init +# shellcheck source=lib/bash/rose_usage +. rose_init rose_init # Print actual command of a command alias @@ -100,10 +102,11 @@ path_lead() { # Print Rose version function print_version() { - echo "Rose $ROSE_VERSION ($ROSE_HOME)" + echo "Rose $ROSE_VERSION ($ROSE_HOME_BIN)" } #------------------------------------------------------------------------------- +# if "rose x" input then set UTIL=x UTIL="help" if (($# > 0)); then UTIL=$1 @@ -147,10 +150,8 @@ version|--version|-V) exit :;; doc) - PATH=$(path_lead "${PATH:-}" "$ROSE_HOME_BIN") - PYTHONPATH=$(path_lead "${PYTHONPATH:-}" "$ROSE_HOME/lib/python") ROSE_UTIL=$UTIL - export PATH PYTHONPATH ROSE_UTIL + export ROSE_UTIL ns_len=$(( ${#ROSE_NS}+2 )) for U in $(cd "$ROSE_HOME_BIN" && ls "$ROSE_NS-"*); do NAME="$(sed "s/^$ROSE_NS-\\(.*\\)\$/\1/" <<<"$U")" @@ -185,8 +186,6 @@ if (($# > 0)) && [[ $1 == '--help' || $1 == '-h' ]]; then help_util "$UTIL" exit fi -PATH=$(path_lead "${PATH:-}" "$ROSE_HOME_BIN") -PYTHONPATH=$(path_lead "${PYTHONPATH:-}" "$ROSE_HOME/lib/python") ROSE_UTIL=$UTIL -export PATH PYTHONPATH ROSE_UTIL +export ROSE_UTIL exec "$COMMAND" "$@" diff --git a/bin/rose-app-run b/bin/rose-app-run index 0cab2ee068..b71f9fb897 100755 --- a/bin/rose-app-run +++ b/bin/rose-app-run @@ -86,4 +86,4 @@ # optional ROSE_FILE_INSTALL_ROOT # If specified, change to the specified directory to install files. #------------------------------------------------------------------------------- -exec python3 -m rose.app_run "$@" +exec python3 -m metomi.rose.app_run "$@" diff --git a/bin/rose-app-upgrade b/bin/rose-app-upgrade index e2aebce3e8..db75708282 100755 --- a/bin/rose-app-upgrade +++ b/bin/rose-app-upgrade @@ -48,4 +48,4 @@ # optional ROSE_META_PATH # Prepend `$ROSE_META_PATH` to the metadata search path. #------------------------------------------------------------------------------- -exec python3 -m rose.upgrade "$@" +exec python3 -m metomi.rose.upgrade "$@" diff --git a/bin/rose-config b/bin/rose-config index a903541dec..5ec623dcb0 100755 --- a/bin/rose-config +++ b/bin/rose-config @@ -99,4 +99,4 @@ # optional ROSE_META_PATH # Prepend `$ROSE_META_PATH` to the metadata search path. #------------------------------------------------------------------------------- -exec python3 -m rose.config_cli "$@" +exec python3 -m metomi.rose.config_cli "$@" diff --git a/bin/rose-config-diff b/bin/rose-config-diff index f6d6fa3af1..e5e61506c2 100755 --- a/bin/rose-config-diff +++ b/bin/rose-config-diff @@ -114,4 +114,4 @@ # rose config-diff --ignore=namelist:bar --ignore=namelist:baz ... # #------------------------------------------------------------------------------- -exec python3 -m rose.config_diff "$@" +exec python3 -m metomi.rose.config_diff "$@" diff --git a/bin/rose-config-dump b/bin/rose-config-dump index 4c40b503f7..61184c9bbb 100755 --- a/bin/rose-config-dump +++ b/bin/rose-config-dump @@ -47,4 +47,4 @@ # --quiet, -q # Decrement verbosity. Do not report modified files. #------------------------------------------------------------------------------- -exec python3 -m rose.config_dump "$@" +exec python3 -m metomi.rose.config_dump "$@" diff --git a/bin/rose-config-edit b/bin/rose-config-edit index 5e6b5cdcaf..60337f6619 100755 --- a/bin/rose-config-edit +++ b/bin/rose-config-edit @@ -70,4 +70,4 @@ # optional ROSE_META_PATH # Prepend `$ROSE_META_PATH` to the metadata search path. #------------------------------------------------------------------------------- -exec python3 -m rose.config_editor.main "$@" +exec python3 -m metomi.rose.config_editor.main "$@" diff --git a/bin/rose-date b/bin/rose-date index e10107bb47..33a464c563 100755 --- a/bin/rose-date +++ b/bin/rose-date @@ -177,7 +177,7 @@ PRINT FORMAT import sys import os -from isodatetime.main import main as iso_main +from metomi.isodatetime.main import main as iso_main def main(): diff --git a/bin/rose-env-cat b/bin/rose-env-cat index 109a833e69..94af788510 100755 --- a/bin/rose-env-cat +++ b/bin/rose-env-cat @@ -48,4 +48,4 @@ # variable with the value of `STRING`, (which can be an empty string), # instead of failing. #------------------------------------------------------------------------------- -exec python3 -m rose.env_cat "$@" +exec python3 -m metomi.rose.env_cat "$@" diff --git a/bin/rose-host-select b/bin/rose-host-select index 5c04a8e921..d4f410b029 100755 --- a/bin/rose-host-select +++ b/bin/rose-host-select @@ -91,4 +91,4 @@ # Set the timeout in seconds of SSH commands to hosts. # (default=10.0) #------------------------------------------------------------------------------- -exec python3 -m rose.host_select "$@" +exec python3 -m metomi.rose.host_select "$@" diff --git a/bin/rose-macro b/bin/rose-macro index ed19aa5875..b7cb7b0487 100755 --- a/bin/rose-macro +++ b/bin/rose-macro @@ -76,4 +76,4 @@ # optional ROSE_META_PATH # Prepend `$ROSE_META_PATH` to the metadata search path. #------------------------------------------------------------------------------- -exec python3 -m rose.macro "$@" +exec python3 -m metomi.rose.macro "$@" diff --git a/bin/rose-metadata-check b/bin/rose-metadata-check index 8bd3a9d950..02db3ef5b8 100755 --- a/bin/rose-metadata-check +++ b/bin/rose-metadata-check @@ -40,4 +40,4 @@ # metadata e.g.`env=FOO` or `namelist:bar`. If specified, only # this section will be checked. #------------------------------------------------------------------------------- -exec python3 -m rose.metadata_check "$@" +exec python3 -m metomi.rose.metadata_check "$@" diff --git a/bin/rose-metadata-gen b/bin/rose-metadata-gen index 226dc0a4a7..0c7e876900 100755 --- a/bin/rose-metadata-gen +++ b/bin/rose-metadata-gen @@ -46,4 +46,4 @@ # every setting e.g. `compulsory=true`. If `=VALUE` is missing, # the property will be set to a null string in each setting. #------------------------------------------------------------------------------- -exec python3 -m rose.metadata_gen "$@" +exec python3 -m metomi.rose.metadata_gen "$@" diff --git a/bin/rose-metadata-graph b/bin/rose-metadata-graph index 3d3ac808e2..1667e67f01 100755 --- a/bin/rose-metadata-graph +++ b/bin/rose-metadata-graph @@ -50,4 +50,4 @@ # optional ROSE_META_PATH # Prepend `$ROSE_META_PATH` to the metadata search path. #------------------------------------------------------------------------------- -exec python3 -m rose.metadata_graph "$@" +exec python3 -m metomi.rose.metadata_graph "$@" diff --git a/bin/rose-mpi-launch b/bin/rose-mpi-launch index 384af3c4a8..cda884ed85 100755 --- a/bin/rose-mpi-launch +++ b/bin/rose-mpi-launch @@ -111,7 +111,8 @@ # DIAGNOSTICS # Return 0 on success, 1 or exit code of the launcher program on failure. #------------------------------------------------------------------------------- -. "$(dirname "$0")/../lib/bash/rose_init" +# shellcheck source=lib/bash/rose_init +. rose_init rose_init rose_log # ------------------------------------------------------------------------------ diff --git a/bin/rose-namelist-dump b/bin/rose-namelist-dump index 56fcd7db18..0660c49d8c 100755 --- a/bin/rose-namelist-dump +++ b/bin/rose-namelist-dump @@ -45,4 +45,4 @@ # -u, --upper # Shorthand for `--case=upper`. #------------------------------------------------------------------------------- -exec python3 -m rose.namelist_dump "$@" +exec python3 -m metomi.rose.namelist_dump "$@" diff --git a/bin/rose-stem b/bin/rose-stem index 211e3a0c47..a2ddc385cb 100755 --- a/bin/rose-stem +++ b/bin/rose-stem @@ -82,4 +82,4 @@ # is intended to specify the revision of `fcm-make` config files. # #------------------------------------------------------------------------------- -exec python3 -m rose.stem "$@" +exec python3 -m metomi.rose.stem "$@" diff --git a/bin/rose-suite-clean b/bin/rose-suite-clean index 4513f1d0e7..a5653a79ac 100755 --- a/bin/rose-suite-clean +++ b/bin/rose-suite-clean @@ -50,4 +50,4 @@ # Return the difference between the number of arguments and number of # successfully cleaned suites, i.e. 0 if all successful. #------------------------------------------------------------------------------- -exec python3 -m rose.suite_clean "$@" +exec python3 -m metomi.rose.suite_clean "$@" diff --git a/bin/rose-suite-cmp-vc b/bin/rose-suite-cmp-vc index 735fb143b9..ac64179494 100755 --- a/bin/rose-suite-cmp-vc +++ b/bin/rose-suite-cmp-vc @@ -50,4 +50,4 @@ # --verbose, -v # Increment verbosity. #------------------------------------------------------------------------------- -exec python3 -m rose.cmp_source_vc "$@" +exec python3 -m metomi.rose.cmp_source_vc "$@" diff --git a/bin/rose-suite-hook b/bin/rose-suite-hook index a569ccec00..9e1f15fb2e 100755 --- a/bin/rose-suite-hook +++ b/bin/rose-suite-hook @@ -51,4 +51,4 @@ # --verbose, -v # Increment verbosity. #------------------------------------------------------------------------------- -exec python3 -m rose.suite_hook "$@" +exec python3 -m metomi.rose.suite_hook "$@" diff --git a/bin/rose-suite-log b/bin/rose-suite-log index 5c99d02daa..5490863a16 100755 --- a/bin/rose-suite-log +++ b/bin/rose-suite-log @@ -64,4 +64,4 @@ # --view # Launch web browser to view suite log. #------------------------------------------------------------------------------- -exec python3 -m rose.suite_log "$@" +exec python3 -m metomi.rose.suite_log "$@" diff --git a/bin/rose-suite-restart b/bin/rose-suite-restart index 372a4de313..f6acfc34ff 100755 --- a/bin/rose-suite-restart +++ b/bin/rose-suite-restart @@ -55,4 +55,4 @@ # --verbose, -v # Increment verbosity. #------------------------------------------------------------------------------- -exec python3 -m rose.suite_restart "$@" +exec python3 -m metomi.rose.suite_restart "$@" diff --git a/bin/rose-suite-run b/bin/rose-suite-run index e41497fa1f..1622a642d3 100755 --- a/bin/rose-suite-run +++ b/bin/rose-suite-run @@ -129,4 +129,4 @@ # * `cylc help register` # * `cylc help run` #------------------------------------------------------------------------------- -exec python3 -m rose.suite_run "$@" +exec python3 -m metomi.rose.suite_run "$@" diff --git a/bin/rose-suite-shutdown b/bin/rose-suite-shutdown index 0762e37ae8..e4bdb257ed 100755 --- a/bin/rose-suite-shutdown +++ b/bin/rose-suite-shutdown @@ -54,4 +54,4 @@ # See the cylc documentation, cylc shutdown for options and details on # `EXTRA-ARGS`. #------------------------------------------------------------------------------- -exec python3 -m rose.suite_control shutdown "$@" +exec python3 -m metomi.rose.suite_control shutdown "$@" diff --git a/bin/rose-task-env b/bin/rose-task-env index 51077eb42e..52b0a837df 100755 --- a/bin/rose-task-env +++ b/bin/rose-task-env @@ -140,4 +140,4 @@ # `env-script = eval $(rose task-env)`. # #------------------------------------------------------------------------------- -exec python3 -m rose.task_env "$@" +exec python3 -m metomi.rose.task_env "$@" diff --git a/bin/rose-task-run b/bin/rose-task-run index 63556584b1..8cd88cb941 100755 --- a/bin/rose-task-run +++ b/bin/rose-task-run @@ -54,4 +54,4 @@ # * `rose app-run` # * `rose task-env` #------------------------------------------------------------------------------- -exec python3 -m rose.task_run "$@" +exec python3 -m metomi.rose.task_run "$@" diff --git a/bin/rose-tutorial b/bin/rose-tutorial index 490d159064..4d9856c3a8 100755 --- a/bin/rose-tutorial +++ b/bin/rose-tutorial @@ -30,7 +30,8 @@ # Make a copy of one of the tutorial SUITE in the cylc-run directory. # #------------------------------------------------------------------------------- -. "$(dirname "$0")/../lib/bash/rose_init" +# shellcheck source=lib/bash/rose_init +. rose_init mkdir -p "$HOME/cylc-run" usage () { diff --git a/bin/rosie-checkout b/bin/rosie-checkout index 3d0e63a881..7b9c5e3add 100755 --- a/bin/rosie-checkout +++ b/bin/rosie-checkout @@ -38,4 +38,4 @@ # --verbose, -v # Increment verbosity. #------------------------------------------------------------------------------- -exec python3 -m rosie.vc checkout "$@" +exec python3 -m metomi.rosie.vc checkout "$@" diff --git a/bin/rosie-create b/bin/rosie-create index 1fbbc9686c..6f07ae38e0 100755 --- a/bin/rosie-create +++ b/bin/rosie-create @@ -70,4 +70,4 @@ # --verbose, -v # Increment verbosity. #------------------------------------------------------------------------------- -exec python3 -m rosie.vc create "$@" +exec python3 -m metomi.rosie.vc create "$@" diff --git a/bin/rosie-delete b/bin/rosie-delete index 7fe1b22fcd..27b73af659 100755 --- a/bin/rosie-delete +++ b/bin/rosie-delete @@ -49,4 +49,4 @@ # --verbose, -v # Increment verbosity. #------------------------------------------------------------------------------- -exec python3 -m rosie.vc delete "$@" +exec python3 -m metomi.rosie.vc delete "$@" diff --git a/bin/rosie-disco b/bin/rosie-disco index 7213726129..272d56a9c7 100755 --- a/bin/rosie-disco +++ b/bin/rosie-disco @@ -38,4 +38,4 @@ # --service-root, -R # (For start only.) Include web service name under root of URL. #------------------------------------------------------------------------------- -exec python3 -m rosie.ws "$@" +exec python3 -m metomi.rosie.ws "$@" diff --git a/bin/rosie-go b/bin/rosie-go index 24158b08f8..e9c322b410 100755 --- a/bin/rosie-go +++ b/bin/rosie-go @@ -67,5 +67,5 @@ # * `rosie lookup` # * `rosie ls` #------------------------------------------------------------------------------- -exec python3 -m rosie.browser.main "$@" +exec python3 -m metomi.rosie.browser.main "$@" diff --git a/bin/rosie-graph b/bin/rosie-graph index 57e47718e3..6308749d0a 100755 --- a/bin/rosie-graph +++ b/bin/rosie-graph @@ -62,4 +62,4 @@ # A suite id to graph. If given, only the suites that are # connected to this id by copy history will be graphed. #------------------------------------------------------------------------------- -exec python3 -m rosie.graph "$@" +exec python3 -m metomi.rosie.graph "$@" diff --git a/bin/rosie-hello b/bin/rosie-hello index b2c93a0d3c..b8e2dcc5f7 100755 --- a/bin/rosie-hello +++ b/bin/rosie-hello @@ -31,4 +31,4 @@ # Specify the name of one or more Rosie web service servers to use. # This option can be used multiple times. #------------------------------------------------------------------------------- -exec python3 -m rosie.ws_client_cli hello "$@" +exec python3 -m metomi.rosie.ws_client_cli hello "$@" diff --git a/bin/rosie-id b/bin/rosie-id index 88f66477a7..27fdd0697b 100755 --- a/bin/rosie-id +++ b/bin/rosie-id @@ -70,4 +70,4 @@ # --next # Print the next available suite ID in the repository #------------------------------------------------------------------------------- -exec python3 -m rosie.suite_id "$@" +exec python3 -m metomi.rosie.suite_id "$@" diff --git a/bin/rosie-lookup b/bin/rosie-lookup index 3a7b51d468..d78f3a42ad 100755 --- a/bin/rosie-lookup +++ b/bin/rosie-lookup @@ -85,4 +85,4 @@ # --verbose, -v # Display full info for each returned suite. #------------------------------------------------------------------------------- -exec python3 -m rosie.ws_client_cli lookup "$@" +exec python3 -m metomi.rosie.ws_client_cli lookup "$@" diff --git a/bin/rosie-ls b/bin/rosie-ls index 4eb5f4b22b..a75e8e34c6 100755 --- a/bin/rosie-ls +++ b/bin/rosie-ls @@ -62,4 +62,4 @@ # --verbose, -v # Display full info for each returned suite. #------------------------------------------------------------------------------- -exec python3 -m rosie.ws_client_cli list_local_suites "$@" +exec python3 -m metomi.rosie.ws_client_cli list_local_suites "$@" diff --git a/bin/rose-make-docs b/etc/bin/rose-make-docs similarity index 98% rename from bin/rose-make-docs rename to etc/bin/rose-make-docs index 223a8a22a5..2c5b6d5e06 100755 --- a/bin/rose-make-docs +++ b/etc/bin/rose-make-docs @@ -95,8 +95,8 @@ set -o pipefail shopt -s extglob # Move into rose directory -cd "$(dirname "$0")/../" - +cd "$(dirname "$0")/../.." +ROSE_HOME=$PWD # Path for virtualenv. VENV_PATH='venv' # Note: update above docs when changing this value # Set to `true` when the virtualenv is being used. @@ -104,7 +104,7 @@ USING_VENV=false # Path to the sphinx directory. SPHINX_PATH=sphinx # pick up official rose version -. "${ROSE_HOME}/rose-version" +ROSE_VERSION="$(python3 -c "import metomi.rose; print(metomi.rose.__version__)")" # documentation root output directory DOCS_DIR="${ROSE_HOME}/doc" @@ -189,7 +189,6 @@ venv-destroy () { version_file () { # output the dictionary {"version": ["build", ...]} in JSON format DOCS_DIR="$1" - ret='{' # scrape filesystem for list of rose versions which have built docs diff --git a/bin/rose-test-battery b/etc/bin/rose-test-battery similarity index 85% rename from bin/rose-test-battery rename to etc/bin/rose-test-battery index e9e621f708..7cd18fdc73 100755 --- a/bin/rose-test-battery +++ b/etc/bin/rose-test-battery @@ -48,21 +48,25 @@ # SEE ALSO # * `prove(1)` #------------------------------------------------------------------------------- -. "$(dirname "$0")/../lib/bash/rose_init" -. "$(dirname "$0")/../lib/bash/rose_log" +# shellcheck source=lib/bash/rose_init +# shellcheck source=lib/bash/rose_log +. rose_init +. rose_log set -eu rose_init + +# Move to folder in which prove command should be run. +TESTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +cd "$TESTDIR/../../" mkdir -p ~/.metomi -if [[ $ROSE_HOME != . ]]; then - info 1 cd "$ROSE_HOME" - cd "$ROSE_HOME" -fi + # Recompile *.pyc files to ensure we are running the current code. -if [[ -w 'lib/python/' ]]; then - find 'lib/python/' -name '*.pyc' -type 'f' -delete - python3 -mcompileall -q 'lib/python/' +# @TODO Consider if this is appropriate in new version +if [[ -w 'metomi/' ]]; then + find 'metomi/' -name '*.pyc' -type 'f' -delete + python3 -mcompileall -q 'metomi' fi if PROVE_OPTIONS=$(rose config t prove-options); then # shellcheck disable=SC2086 diff --git a/etc/rose-bash-completion b/etc/rose-bash-completion index 79f7ce190f..5003139f2e 100644 --- a/etc/rose-bash-completion +++ b/etc/rose-bash-completion @@ -57,7 +57,7 @@ _rose() { else ROSE_DIR=$(__rose_get_rose_dir) fi - ok_subcommands=$(cd $ROSE_DIR/bin && ls $rose_command-* | \ + ok_subcommands=$(cd $ROSE_DIR/ && ls $rose_command-* | \ sed "s/^$rose_command-//g") # Determine the subcommand (e.g. app-run for rose app-run) diff --git a/lib/bash/rose_init b/lib/bash/rose_init old mode 100644 new mode 100755 index 3a58fa10d7..cfff5c737c --- a/lib/bash/rose_init +++ b/lib/bash/rose_init @@ -30,20 +30,17 @@ # * load the "rose_usage" function. # * load any FUNC specified in the argument list. #------------------------------------------------------------------------------- +# shellcheck source=lib/bash/rose_usage +. rose_usage function rose_init() { set -eu - ROSE_NS="$(basename "$0")" - ROSE_HOME_BIN="$(cd "$(dirname "$0")" && pwd)" - ROSE_HOME="$(dirname "$ROSE_HOME_BIN")" - eval "$(grep 'ROSE_VERSION' "$ROSE_HOME/rose-version")" - local DESC= - if DESC="$(cd "$ROSE_HOME" && git describe 2>/dev/null)"; then - ROSE_VERSION=$DESC - fi - export ROSE_HOME ROSE_HOME_BIN ROSE_NS ROSE_VERSION - - local LIB=$ROSE_HOME/lib/bash + ROSE_NS=$(basename "$0") + ROSE_LIB="$(dirname "$(python -c "import metomi.rose; print(metomi.rose.__file__)")")" + ROSE_HOME_BIN=$(dirname "$(command -v rose)") + ROSE_VERSION="$(python3 -c "import metomi.rose; print(metomi.rose.__version__)")" + export ROSE_LIB ROSE_HOME_BIN ROSE_NS ROSE_VERSION + local LIB=$ROSE_LIB if [[ -f $LIB/${FUNCNAME[0]}_site ]]; then . "$LIB/${FUNCNAME[0]}_site" fi @@ -51,7 +48,7 @@ function rose_init() { local ITEM= for ITEM in rose_usage "$@"; do local FILE= - for FILE in $LIB/$ITEM; do + for FILE in $ITEM; do . "$FILE" done done diff --git a/lib/python/isodatetime/__init__.py b/lib/python/isodatetime/__init__.py deleted file mode 100644 index 601916cfbc..0000000000 --- a/lib/python/isodatetime/__init__.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -# ---------------------------------------------------------------------------- -# Copyright (C) 2013-2019 British Crown (Met Office) & Contributors. -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program. If not, see . -# ---------------------------------------------------------------------------- -"""Python ISO 8601 date time parser and data model/manipulation utilities.""" - -__version__ = "2.0.0" diff --git a/lib/python/isodatetime/data.py b/lib/python/isodatetime/data.py deleted file mode 100644 index d349e6055b..0000000000 --- a/lib/python/isodatetime/data.py +++ /dev/null @@ -1,2305 +0,0 @@ -# -*- coding: utf-8 -*- -# ---------------------------------------------------------------------------- -# Copyright (C) 2013-2019 British Crown (Met Office) & Contributors. -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program. If not, see . -# ---------------------------------------------------------------------------- - -"""This provides ISO 8601 data model functionality.""" - - -from . import dumpers -from . import timezone - -from functools import lru_cache - - -class Calendar(object): - - """Store constants for Gregorian calendar date-time calculation.""" - - SECONDS_IN_MINUTE = 60 - MINUTES_IN_HOUR = 60 - HOURS_IN_DAY = 24 - DAYS_IN_WEEK = 7 - DAYS_IN_MONTHS = None # This is set up in the set_* methods. - DAYS_IN_MONTHS_LEAP = None # This is set up in the set_* methods - ROUGH_DAYS_IN_MONTH = 30 # Used for duration conversion, nowhere else. - - LEAP_YEAR_FACTOR_TRUTHS = [(4, True), (100, False), (400, True)] - - MODE_360 = "360day" - MODE_365 = "365day" - MODE_366 = "366day" - MODE_GREGORIAN = "gregorian" - - # {mode: (days_in_months, days_in_months_leap), ...} - MODES = { - MODE_360: (12 * [30], None), - MODE_365: ([31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31], None), - MODE_366: ([31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31], None), - MODE_GREGORIAN: ( - [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31], - [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31], - ), - } - - WEEK_DAY_START_REFERENCE = {"calendar": (2000, 1, 3), - "ordinal": (2000, 3)} - UNIX_EPOCH_DATE_TIME_REFERENCE_PROPERTIES = { - "year": 1970, "time_zone_hour": 0, "time_zone_minute": 0} - - _DEFAULT = None - - @classmethod - def default(cls): - """Return the singleton instance. Create if necessary.""" - if cls._DEFAULT is None: - cls._DEFAULT = cls() - return cls._DEFAULT - - def __init__(self): - self.set_mode() - - def set_mode(self, mode=None): - """Set calendar mode. - - mode -- calendar mode, which can be one of the keys in Calendar.MODES. - If None, default to MODE_GREGORIAN. - - """ - if not mode: - mode = self.MODE_GREGORIAN - days_in_months, days_in_months_leap = self.MODES[mode.lower()] - if days_in_months_leap is None: - days_in_months_leap = days_in_months - self.DAYS_IN_MONTHS = days_in_months - self.DAYS_IN_MONTHS_LEAP = days_in_months_leap - self.mode = mode - - # Recalculate - self.SECONDS_IN_HOUR = self.SECONDS_IN_MINUTE * self.MINUTES_IN_HOUR - self.SECONDS_IN_DAY = self.SECONDS_IN_HOUR * self.HOURS_IN_DAY - self.MINUTES_IN_DAY = self.MINUTES_IN_HOUR * self.HOURS_IN_DAY - self.INDEXED_DAYS_IN_MONTHS = [ - (i + 1, days) for i, days in enumerate(self.DAYS_IN_MONTHS)] - self.INDEXED_DAYS_IN_MONTHS_LEAP = [ - (i + 1, days) for i, days in enumerate(self.DAYS_IN_MONTHS_LEAP)] - self.REVERSED_INDEXED_DAYS_IN_MONTHS = ( - reversed(self.INDEXED_DAYS_IN_MONTHS)) - self.REVERSED_INDEXED_DAYS_IN_MONTHS_LEAP = ( - reversed(self.INDEXED_DAYS_IN_MONTHS)) - self.MONTHS_IN_YEAR = len(self.DAYS_IN_MONTHS) - # No support for MONTHS_IN_YEAR_LEAP (some calendars...) - self.DAYS_IN_YEAR = sum(self.DAYS_IN_MONTHS) - self.ROUGH_DAYS_IN_YEAR = self.DAYS_IN_YEAR - self.DAYS_IN_YEAR_LEAP = sum(self.DAYS_IN_MONTHS_LEAP) - self.HOURS_IN_YEAR = self.DAYS_IN_YEAR * self.HOURS_IN_DAY - self.MINUTES_IN_YEAR = self.DAYS_IN_YEAR * self.MINUTES_IN_DAY - self.SECONDS_IN_YEAR = self.DAYS_IN_YEAR * self.SECONDS_IN_DAY - self.HOURS_IN_YEAR_LEAP = self.DAYS_IN_YEAR_LEAP * self.HOURS_IN_DAY - self.MINUTES_IN_YEAR_LEAP = ( - self.DAYS_IN_YEAR_LEAP * self.MINUTES_IN_DAY) - self.SECONDS_IN_YEAR_LEAP = ( - self.DAYS_IN_YEAR_LEAP * self.SECONDS_IN_DAY) - - def __repr__(self): - return "<%s-%s>" % (type(self).__name__, self.mode) - - -CALENDAR = Calendar.default() - - -TIMEPOINT_DUMPER_MAP = { - 0: dumpers.TimePointDumper(num_expanded_year_digits=0), - 2: dumpers.TimePointDumper(num_expanded_year_digits=2) -} - - -class BadInputError(ValueError): - - """An error raised when constructor inputs are invalid.""" - - CONFLICT = "Conflicting input: {0} but have {1}" - INT_CAST = "Invalid input for {0}: {1}: {2}" - INT_REMAINDER = "Non-integer like number for {0}: {1}" - MISSING = "Missing input: {0} needs {1}" - OUT_OF_BOUNDS = "Invalid input (out of bounds): {0}: {1}" - RECURRENCE = "Invalid recurrence info: {0}" - TYPE = "Invalid type for {0}: {1}{2}" - VALUES = "Invalid input for {0}: {1}: allowed: {2}" - - def __str__(self): - format_string = self.args[0] - format_args = self.args[1:] - return format_string.format(*format_args) - - -class TimeRecurrence(object): - - """Represent a recurring duration.""" - - __slots__ = ("repetitions", "start_point", "duration", "end_point", - "min_point", "max_point", "format_number") - - def __init__(self, repetitions=None, start_point=None, - duration=None, end_point=None, min_point=None, - max_point=None): - inputs = ( - (repetitions, "repetitions", None, int), - (start_point, "start_point", None, TimePoint), - (duration, "duration", None, Duration), - (end_point, "end_point", None, TimePoint), - (min_point, "min_point", None, TimePoint), - (max_point, "max_point", None, TimePoint) - ) - _type_checker(*inputs) - self.repetitions = repetitions - self.start_point = start_point - self.duration = duration - self.end_point = end_point - self.min_point = min_point - self.max_point = max_point - self.format_number = None - if self.duration is None: - # First form. - self.format_number = 1 - start_year, start_days = self.start_point.get_ordinal_date() - start_seconds = self.start_point.get_second_of_day() - self.end_point.set_time_zone(self.start_point.time_zone) - end_year, end_days = self.end_point.get_ordinal_date() - end_seconds = self.end_point.get_second_of_day() - diff_days = end_days - start_days - if end_year > start_year: - diff_days += get_days_in_year_range(start_year, end_year - 1) - diff_seconds = end_seconds - start_seconds - if diff_seconds < 0: - diff_days -= 1 - diff_seconds += CALENDAR.SECONDS_IN_DAY - if diff_seconds >= CALENDAR.SECONDS_IN_DAY: - diff_days += 1 - diff_seconds -= CALENDAR.SECONDS_IN_DAY - if self.repetitions == 1: - self.duration = Duration(years=0) - else: - diff_days_float = diff_days / float( - self.repetitions - 1) - diff_seconds_float = diff_seconds / float( - self.repetitions - 1) - diff_days = int(diff_days_float) - diff_seconds_float += ( - diff_days_float - diff_days) * CALENDAR.SECONDS_IN_DAY - self.duration = Duration( - days=diff_days, seconds=diff_seconds_float) - elif self.end_point is None: - # Third form. - self.format_number = 3 - if self.repetitions is not None: - self.end_point = ( - self.start_point + self.duration * (self.repetitions - 1)) - elif self.start_point is None: - # Fourth form. - self.format_number = 4 - if self.repetitions is not None: - self.start_point = ( - self.end_point - self.duration * (self.repetitions - 1)) - else: - raise BadInputError( - BadInputError.RECURRENCE, [i[:2] for i in inputs]) - - def get_is_valid(self, timepoint): - """Return whether the timepoint is valid for this recurrence.""" - if not self._get_is_in_bounds(timepoint): - return False - for iter_timepoint in self.__iter__(): - if iter_timepoint == timepoint: - return True - if self.start_point is None and iter_timepoint < timepoint: - return False - if self.end_point is None and iter_timepoint > timepoint: - return False - return False - - def get_next(self, timepoint): - """Return the next timepoint after this timepoint, or None.""" - if self.repetitions == 1 or timepoint is None: - return None - next_timepoint = timepoint + self.duration - if self._get_is_in_bounds(next_timepoint): - return next_timepoint - if self.format_number == 1 and next_timepoint > self.end_point: - diff = next_timepoint - self.end_point - if (2 * diff < self.duration and - self._get_is_in_bounds(self.end_point)): - return self.end_point - return None - - def get_prev(self, timepoint): - """Return the previous timepoint before this timepoint, or None.""" - if self.repetitions == 1 or timepoint is None: - return None - prev_timepoint = timepoint - self.duration - if self._get_is_in_bounds(prev_timepoint): - return prev_timepoint - return None - - def __getitem__(self, index): - if index < 0 or not isinstance(index, int): - raise IndexError( - "Unsupported index for TimeRecurrence") - for i, point in enumerate(self.__iter__()): - if index == i: - return point - raise IndexError( - "Invalid index for TimeRecurrence") - - def _get_is_in_bounds(self, timepoint): - """Return whether the timepoint is within this recurrence series.""" - if timepoint is None: - return False - if self.start_point is not None and timepoint < self.start_point: - return False - if self.min_point is not None and timepoint < self.min_point: - return False - if self.max_point is not None and timepoint > self.max_point: - return False - if self.end_point is not None and timepoint > self.end_point: - return False - return True - - def __iter__(self): - if self.start_point is None: - point = self.end_point - in_reverse = True - else: - point = self.start_point - in_reverse = False - - if self.repetitions == 1 or not self.duration: - if self._get_is_in_bounds(point): - yield point - point = None - - while point is not None: - if self._get_is_in_bounds(point): - yield point - else: - break - if in_reverse: - point = self.get_prev(point) - else: - point = self.get_next(point) - - def __str__(self): - if self.repetitions is None: - prefix = "R/" - else: - prefix = "R" + str(self.repetitions) + "/" - if self.format_number == 1: - return prefix + str(self.start_point) + "/" + str(self.end_point) - elif self.format_number == 3: - return prefix + str(self.start_point) + "/" + str(self.duration) - elif self.format_number == 4: - return prefix + str(self.duration) + "/" + str(self.end_point) - return "R/?/?" - - -class Duration(object): - - """Represent a duration or period of time. - - Keyword arguments: - years (default 0): number of calendar years in the duration (an - inexact unit) - months (default 0): number of calendar months in the duration (also - an inexact unit) - weeks (default 0): number of weeks in the duration - cannot be - used in conjunction with other units (use multiples of 7 days - instead) - days (default 0): number of days in the duration - hours (default 0): number of hours in the duration - minutes (default 0): number of minutes in the duration - seconds (default 0): number of seconds in the duration - standardize (default False): boolean that, if True, switches on - adjusting the attributes so that small units have minimal values. - For example, 3664.4 seconds would become 1 hour, 1 minute, and 4.4 - seconds. Attributes will not adjust for units that are inexact - (months and years). - - """ - - DATA_ATTRIBUTES = [ - "years", "months", "weeks", "days", "hours", "minutes", "seconds"] - - __slots__ = DATA_ATTRIBUTES - - def __init__(self, years=0, months=0, weeks=0, days=0, - hours=0.0, minutes=0.0, seconds=0.0, standardize=False): - _type_checker( - (years, "years", int, float, None), - (months, "months", int, float, None), - (weeks, "weeks", int, float, None), - (days, "days", int, float, None), - (hours, "hours", int, float, None), - (minutes, "minutes", int, float, None), - (seconds, "seconds", int, float, None) - ) - self.years = years - self.months = months - self.weeks = None - self.days = days - if weeks is not None: - if days is None: - self.days = CALENDAR.DAYS_IN_WEEK * weeks - else: - self.days += CALENDAR.DAYS_IN_WEEK * weeks - self.hours = hours - self.minutes = minutes - self.seconds = seconds - if (not self.years and not self.months and not self.hours and - not self.minutes and not self.seconds and - weeks and not days): - self.weeks = self.days // CALENDAR.DAYS_IN_WEEK - self.years, self.months, self.days = (None, None, None) - self.hours, self.minutes, self.seconds = (None, None, None) - if standardize: - if self.seconds: - num_minutes, self.seconds = divmod( - self.seconds, CALENDAR.SECONDS_IN_MINUTE) - if self.minutes is None: - self.minutes = 0 - self.minutes += num_minutes - if self.minutes: - num_hours, self.minutes = divmod( - self.minutes, CALENDAR.MINUTES_IN_HOUR) - if self.hours is None: - self.hours = 0 - self.hours += num_hours - if self.hours: - num_days, self.hours = divmod( - self.hours, CALENDAR.HOURS_IN_DAY) - if self.days is None: - self.days = 0 - self.days += num_days - - def copy(self): - """Return an unlinked copy of this instance.""" - return Duration( - years=self.years, months=self.months, weeks=self.weeks, - days=self.days, hours=self.hours, minutes=self.minutes, - seconds=self.seconds) - - def get_days_and_seconds(self): - """Return a roughly-converted duration in days and seconds. - - This cannot be accurate for non-uniform units such as years and - months, and may yield incorrect results if used for comparisons - derived from durations using these units. - - Seconds are returned in the range - 0 <= seconds < CALENDAR.SECONDS_IN_DAY, which means that a - Duration which has self.seconds = CALENDAR.SECONDS_IN_DAY + - 100 will return 1 day, 100 seconds or (1, 100) from this - method. - - """ - # TODO: Implement error calculation for the below quantities. - new = self.copy() - new.to_days() - new_days = (new.years * CALENDAR.ROUGH_DAYS_IN_YEAR + - new.months * CALENDAR.ROUGH_DAYS_IN_MONTH + - new.days) - new_seconds = (new.hours * CALENDAR.SECONDS_IN_HOUR + - new.minutes * CALENDAR.SECONDS_IN_MINUTE + - new.seconds) - diff_days, new_seconds = divmod(new_seconds, CALENDAR.SECONDS_IN_DAY) - new_days += diff_days - return new_days, new_seconds - - def get_seconds(self): - """Return a roughly-converted duration in seconds. - - This is not rigorous when converting from non-uniform units - such as years and months. - - """ - days, seconds = self.get_days_and_seconds() - return days * CALENDAR.SECONDS_IN_DAY + seconds - - def get_is_in_weeks(self): - """Return whether we are in week representation.""" - return self.weeks is not None - - def to_days(self): - """Convert to day representation rather than weeks.""" - if self.get_is_in_weeks(): - for attribute in ["years", "months", "hours", - "minutes", "seconds"]: - if getattr(self, attribute) is None: - setattr(self, attribute, 0) - self.days = self.weeks * CALENDAR.DAYS_IN_WEEK - self.weeks = None - - def to_weeks(self): - """Convert to week representation (warning: use with caution).""" - if not self.get_is_in_weeks(): - self.weeks = self.days // CALENDAR.DAYS_IN_WEEK - self.years, self.months, self.days = (None, None, None) - self.hours, self.minutes, self.seconds = (None, None, None) - - def __abs__(self): - new = self.copy() - for attribute in new.DATA_ATTRIBUTES: - attr_value = getattr(new, attribute) - if attr_value is not None: - setattr(new, attribute, abs(attr_value)) - return new - - def __add__(self, other): - new = self.copy() - if isinstance(other, Duration): - if new.get_is_in_weeks(): - if other.get_is_in_weeks(): - new.weeks += other.weeks - return new - new.to_days() - elif other.get_is_in_weeks(): - other = other.copy() - other.to_days() - new.years += other.years - new.months += other.months - new.days += other.days - new.hours += other.hours - new.minutes += other.minutes - new.seconds += other.seconds - return new - if isinstance(other, TimePoint): - return other + new - raise TypeError( - "Invalid type for addition: " + - "'%s' should be Duration or TimePoint." % - type(other).__name__ - ) - - def __sub__(self, other): - return self + -1 * other - - def __mul__(self, other): - # TODO: support float multiplication? - if not isinstance(other, int): - raise TypeError( - "Invalid type for multiplication: " + - "'%s' should be integer." % - type(other).__name__ - ) - new = self.copy() - for attr in new.DATA_ATTRIBUTES: - value = getattr(new, attr) - if value is not None: - setattr(new, attr, value * other) - return new - - def __rmul__(self, other): - return self.__mul__(other) - - def __floordiv__(self, other): - # TODO: support float division? - if not isinstance(other, int): - raise TypeError( - "Invalid type for division: " + - "'%s' should be integer." % - type(other).__name__ - ) - new = self.copy() - if self.get_is_in_weeks(): - new.weeks //= other - return new - new.years //= other - new.months //= other - new.days //= other - new.hours //= other - new.minutes //= other - new.seconds //= other - return new - - def __eq__(self, other: "Duration") -> bool: - my_data = self.get_days_and_seconds() - other_data = other.get_days_and_seconds() - return my_data == other_data - - def __ne__(self, other: "Duration") -> bool: - return not self.__eq__(other) - - def __lt__(self, other: "Duration") -> bool: - my_data = self.get_days_and_seconds() - other_data = other.get_days_and_seconds() - return my_data < other_data - - def __le__(self, other: "Duration") -> bool: - my_data = self.get_days_and_seconds() - other_data = other.get_days_and_seconds() - return my_data <= other_data - - def __gt__(self, other: "Duration") -> bool: - my_data = self.get_days_and_seconds() - other_data = other.get_days_and_seconds() - return my_data > other_data - - def __ge__(self, other: "Duration") -> bool: - my_data = self.get_days_and_seconds() - other_data = other.get_days_and_seconds() - return my_data >= other_data - - def __bool__(self): - for attr in ["years", "months", "weeks", "days", "hours", - "minutes", "seconds"]: - if getattr(self, attr, None): - return True - return False - - def __str__(self): - start_string = "P" - content_string = "" - - # Handle negative durations. - is_fully_negative = False - for attribute in self.DATA_ATTRIBUTES: - attr_value = getattr(self, attribute) - if attr_value is not None: - if attr_value > 0: - is_fully_negative = False - break - if attr_value < 0: - is_fully_negative = True - if is_fully_negative: - # Support negative durations as extensions to the standard. - return "-" + str(abs(self)) - - # Weeks are not combined with any other unit. - if self.get_is_in_weeks(): - return (start_string + str(self.weeks) + "W").replace(".", ",") - - for prop_, unit in [("years", "Y"), ("months", "M"), ("days", "D"), - ("hours", "H"), ("minutes", "M"), - ("seconds", "S")]: - prop_val = getattr(self, prop_) - if prop_val: - if int(prop_val) == prop_val: - content_string += str(int(prop_val)) + unit - else: - content_string += str(prop_val) + unit - if prop_ == "days": - content_string += "T" - - if content_string == "T": - # No content, zero duration. - content_string = "0Y" - elif content_string.endswith("T"): - # No time unit information, so strip the delimiter. - content_string = content_string[:-1] - - total_string = start_string + content_string - return total_string.replace(".", ",") - - -class TimeZone(Duration): - - """Represent a time zone offset from UTC. - - Keyword arguments: - hours, minutes: integers (default 0) denoting the hour and minute - component of the offset from UTC. These may be positive, zero, or - negative, as required. Note that a negative UTC offset should have - both hours and minutes as zero or negative integers. - unknown: a boolean that represents an unknown TimeZone. Some - operations and comparisons may fail when this is True. - - """ - - __slots__ = ['unknown'] + Duration.__slots__ - - def __init__(self, hours=0, minutes=0, unknown=False): - self.unknown = unknown - super(TimeZone, self).__init__(hours=hours, minutes=minutes) - - def copy(self): - """Return an unlinked copy of this instance.""" - return TimeZone(hours=self.hours, minutes=self.minutes, - unknown=self.unknown) - - def __str__(self): - if self.unknown: - return "" - if self.hours == 0 and self.minutes == 0: - return "Z" - else: - time_string = "+%02d:%02d" - if self.hours < 0 or (self.hours == 0 and self.minutes < 0): - time_string = "-%02d:%02d" - return time_string % (abs(self.hours), abs(self.minutes)) - - def __repr__(self): - return "" - - -class TimePoint(object): - - """Represent an instant in time. - - An ISO 8601 date/time instant can be represented in three - separate ways: - Calendar date: calendar year, calendar month, - calendar day of the month - Ordinal date: calendar year, calendar day of the year - Week date: calendar (week) year, calendar week, - calendar day of the week (note: week years are not identical to - calendar years). - - This class maintains a date/time instant in the original - representation with which it was invoked - so it may be in any of - these formats. See the TimePoint.to_*_date methods for internal - conversions between formats. - - Where properties are not given (consistent with ISO 8601 reduced - precision dates), they will be given the expected defaults if - truncation is not specified. For example, if only the year and the - month_of_year is given, the day_of_month will be set to 1. - - Time zone information defaults to UTC. It is essential to provide it - unless you are happy with this behaviour. A date/time - representation is ambiguous without it. - - Keyword arguments (usually default to None if not provided): - expanded_year_digits (default 0) - an agreed-upon number of extra - digits to represent the year, beyond the default of 4. For example, - a value of 2 would suggest representing the year 2000 as 002000. - year - a positive or negative integer. Note that ISO 8601 implies - using non-zero expanded_year_digits when using negative integers. - Remember we are using the proleptic Gregorian calendar, with a year - zero which does not exist in standard 1 BC => 1 AD usage - so 2 BC - should be represented as -1. - month_of_year - an integer between 1 and 12 inclusive, if using the - calendar date representation. - week_of_year - an integer between 1 and 52/53 (depending on the - year), if using the week date representation. - day_of_year - an integer between 1 and 365/366 (depending on the - year), if using the ordinal date representation. - day_of_month - an integer between 1 and 28/29/30/31 (depending on - the month), if using the calendar date representation. - day_of_week - an integer between 1 and 7, if using the week date - representation. - hour_of_day - an integer between 1 and 24. - hour_of_day_decimal - a float between 0 and 1, if using decimal - accuracy for hours. Note that you should not provide lower units - such as minute_of_hour or second_of_minute when using this. - minute_of_hour - an integer between 0 and 59. - minute_of_hour_decimal - a float between 0 and 1, if using decimal - accuracy for minutes. Note that you should not provide lower units - such as second_of_minute when using this. - second_of_minute - an integer between 0 and 59 (note: no support - for leap seconds at 60 yet) - second_of_minute_decimal - a float between 0 and 1, if using decimal - accuracy for seconds. - time_zone_hour - (default 0) an integer denoting the hour time zone - offset from UTC. Note that unless this is a truncated - representation, 0 will be assumed if this is not provided. - time_zone_minute - (default 0) an integer between 0 and 59 denoting - the minute component of the time zone offset from UTC. - dump_format - a custom format string to control the stringification - of the timepoint. See isodatetime.parser_spec for more details. - truncated - (default False) a boolean denoting whether the - date/time instant has purposefully incomplete information - (ISO 8601:2000 truncation). - truncated_dump_format - a custom format string to control the - stringification of the timepoint if it is truncated. See - isodatetime.parser_spec for more details. - truncated_property - a string that can either be "year_of_decade" - or "year_of_century". This is used for truncated representations to - distinguish between the two ways of truncating the year. - is_empty_instance - if True, do not set any properties yet. These - should be set as part of a copy operation. - """ - - DATA_ATTRIBUTES = [ - "expanded_year_digits", "year", "month_of_year", - "day_of_year", "day_of_month", "day_of_week", - "week_of_year", "hour_of_day", "minute_of_hour", - "second_of_minute", "truncated", "truncated_property", - "truncated_dump_format", "dump_format", "time_zone" - ] - - __slots__ = DATA_ATTRIBUTES - - def __init__(self, expanded_year_digits=0, year=None, month_of_year=None, - week_of_year=None, day_of_year=None, day_of_month=None, - day_of_week=None, hour_of_day=None, hour_of_day_decimal=None, - minute_of_hour=None, minute_of_hour_decimal=None, - second_of_minute=None, second_of_minute_decimal=None, - time_zone_hour=None, time_zone_minute=None, - dump_format=None, truncated=False, - truncated_dump_format=None, truncated_property=None, - is_empty_instance=False): - if is_empty_instance: - # This has been created for a copy - set properties later. - return - if (dump_format is not None and not - isinstance(dump_format, str)): - raise BadInputError( - BadInputError.TYPE, - "dump_format", repr(dump_format), type(dump_format)) - if (truncated_dump_format is not None and not - isinstance(truncated_dump_format, str)): - raise BadInputError( - BadInputError.TYPE, - "truncated_dump_format", repr(truncated_dump_format), - type(truncated_dump_format) - ) - if (truncated_property is not None and - truncated_property not in ["year_of_decade", - "year_of_century"]): - raise BadInputError( - BadInputError.VALUES, "truncated_property", - repr(truncated_property), - "'year_of_decade' or 'year_of_century'") - _type_checker( - (expanded_year_digits, "expanded_year_digits", int), - (year, "year", None, int), - (month_of_year, "month_of_year", None, int), - (week_of_year, "week_of_year", None, int), - (day_of_year, "day_of_year", None, int), - (day_of_month, "day_of_month", None, int), - (day_of_week, "day_of_week", None, int), - (hour_of_day, "hour_of_day", None, int, float), - (hour_of_day_decimal, "hour_of_day_decimal", None, float), - (minute_of_hour, "minute_of_hour", None, int, float), - (minute_of_hour_decimal, "minute_of_hour_decimal", None, float), - (second_of_minute, "second_of_minute", None, int, float), - (second_of_minute_decimal, "second_of_minute_decimal", None, - float), - (time_zone_hour, "time_zone_hour", None, int), - (time_zone_minute, "time_zone_minute", None, int) - ) - self.dump_format = dump_format - self.expanded_year_digits = _int_caster(expanded_year_digits, - "expanded_year_digits") - self.truncated = truncated - self.truncated_dump_format = truncated_dump_format - self.truncated_property = truncated_property - self.year = _int_caster(year, "year", allow_none=True) - self.month_of_year = _int_caster(month_of_year, "year", - allow_none=True) - self.day_of_year = _int_caster(day_of_year, "day_of_year", - allow_none=True) - self.day_of_month = _int_caster(day_of_month, "day_of_month", - allow_none=True) - self.day_of_week = _int_caster(day_of_week, "day_of_week", - allow_none=True) - self.week_of_year = _int_caster(week_of_year, "week_of_year", - allow_none=True) - self.hour_of_day = _int_caster(hour_of_day, "hour_of_day", - allow_none=True) - if hour_of_day_decimal is not None: - if self.hour_of_day is None: - raise BadInputError( - BadInputError.MISSING, "hour_of_day_decimal", - "hour_of_day") - self.hour_of_day += float(hour_of_day_decimal) - if minute_of_hour is not None: - raise BadInputError( - BadInputError.CONFLICT, "minute_of_hour", - "hour_of_day_decimal") - if second_of_minute is not None: - raise BadInputError( - BadInputError.CONFLICT, "second_of_minute", - "hour_of_day_decimal") - if minute_of_hour_decimal is not None: - if minute_of_hour is None: - raise BadInputError( - BadInputError.MISSING, "minute_of_hour_decimal", - "minute_of_hour") - self.minute_of_hour = _int_caster( - minute_of_hour, "minute_of_hour") - self.minute_of_hour += float(minute_of_hour_decimal) - if second_of_minute is not None: - raise BadInputError( - BadInputError.CONFLICT, "second_of_minute", - "minute_of_hour_decimal") - else: - self.minute_of_hour = _int_caster( - minute_of_hour, "minute_of_hour", allow_none=True) - if second_of_minute_decimal is not None: - if second_of_minute is None: - raise BadInputError( - BadInputError.MISSING, - "second_of_minute_decimal", - "second_of_minute") - self.second_of_minute = _int_caster(second_of_minute, - "second_of_minute") - self.second_of_minute += float(second_of_minute_decimal) - else: - self.second_of_minute = _int_caster(second_of_minute, - "second_of_minute", - allow_none=True) - if not self.truncated: - if self.hour_of_day is None: - self.hour_of_day = 0 - if hour_of_day_decimal is None and self.minute_of_hour is None: - self.minute_of_hour = 0 - if (hour_of_day_decimal is None and - minute_of_hour_decimal is None and - self.second_of_minute is None): - self.second_of_minute = 0 - self.time_zone = TimeZone() - has_unknown_tz = True - if time_zone_hour is not None: - has_unknown_tz = False - self.time_zone.hours = _int_caster(time_zone_hour, - "time_zone_hour") - if time_zone_minute is not None: - has_unknown_tz = False - self.time_zone.minutes = _int_caster(time_zone_minute, - "time_zone_minute") - self.time_zone.unknown = self.truncated and has_unknown_tz - if not self.truncated: - # Reduced precision date - e.g. 1970 - assume Jan 1, etc. - if (self.month_of_year is None and self.week_of_year is None and - self.day_of_year is None): - self.month_of_year = 1 - if self.month_of_year is not None and self.day_of_month is None: - self.day_of_month = 1 - if self.week_of_year is not None and self.day_of_week is None: - self.day_of_week = 1 - - def get_is_calendar_date(self): - """Return whether this is in years, month-of-year, day-of-month.""" - return self.month_of_year is not None - - def get_is_ordinal_date(self): - """Return whether this is in years, day-of-the year format.""" - return self.day_of_year is not None - - def get_is_week_date(self): - """Return whether this is in years, week-of-year, day-of-week.""" - return self.week_of_year is not None - - def get_calendar_date(self): - """Return the year, month-of-year and day-of-month for this date.""" - if self.get_is_calendar_date(): - return self.year, self.month_of_year, self.day_of_month - if self.get_is_ordinal_date(): - return get_calendar_date_from_ordinal_date(self.year, - self.day_of_year) - if self.get_is_week_date(): - return get_calendar_date_from_week_date(self.year, - self.week_of_year, - self.day_of_week) - - def get_hour_minute_second(self): - """Return the time of day expressed in hours, minutes, seconds.""" - hour_of_day = self.hour_of_day - minute_of_hour = self.minute_of_hour - second_of_minute = self.second_of_minute - if second_of_minute is None: - if minute_of_hour is None: - hour_decimals = hour_of_day - int(hour_of_day) - hour_of_day = float(int(hour_of_day)) - minute_of_hour = CALENDAR.MINUTES_IN_HOUR * hour_decimals - minute_decimals = minute_of_hour - int(minute_of_hour) - minute_of_hour = float(int(minute_of_hour)) - second_of_minute = CALENDAR.SECONDS_IN_MINUTE * minute_decimals - return hour_of_day, minute_of_hour, second_of_minute - - def get_ordinal_date(self): - """Return the year, day-of-year for this date.""" - if self.get_is_calendar_date(): - return get_ordinal_date_from_calendar_date(self.year, - self.month_of_year, - self.day_of_month) - if self.get_is_ordinal_date(): - return self.year, self.day_of_year - if self.get_is_week_date(): - return get_ordinal_date_from_week_date(self.year, - self.week_of_year, - self.day_of_week) - - def get(self, property_name): - """Return a calculated value for property name.""" - if property_name == "expanded_year_digits": - return abs(self.year) / 10000 - if property_name == "year_sign": - return "+" if self.year >= 0 else "-" - if property_name == "century": - return (abs(self.year) % 10000) // 100 - if property_name == "year_of_century": - return abs(self.year) % 100 - if property_name == "month_of_year": - if self.month_of_year is not None: - return self.month_of_year - return self.get_calendar_date()[1] - if property_name == "day_of_year": - if self.day_of_year is not None: - return self.day_of_year - return self.get_ordinal_date()[1] - if property_name == "day_of_month": - if self.day_of_month is not None: - return self.day_of_month - return self.get_calendar_date()[2] - if property_name == "week_of_year": - if self.week_of_year is not None: - return self.week_of_year - return self.get_week_date()[1] - if property_name == "day_of_week": - if self.day_of_week is not None: - return self.day_of_week - return self.get_week_date()[2] - if property_name == "year_of_decade": - return abs(self.year) % 10 - if property_name == "decade_of_century": - return (abs(self.year) % 100 - abs(self.year) % 10) // 10 - if property_name == "minute_of_hour": - if self.minute_of_hour is None: - return self.get_hour_minute_second()[1] - return int(self.minute_of_hour) - if property_name == "hour_of_day": - return int(self.hour_of_day) - if property_name == "hour_of_day_decimal_string": - string = "%f" % (float(self.hour_of_day) - int(self.hour_of_day)) - string = string.replace("0.", "", 1).rstrip("0") - if not string: - return "0" - return string - if property_name == "minute_of_hour_decimal_string": - string = "%f" % (float(self.minute_of_hour) - - int(self.minute_of_hour)) - string = string.replace("0.", "", 1).rstrip("0") - if not string: - return "0" - return string - if property_name == "second_of_minute": - if self.second_of_minute is None: - return self.get_hour_minute_second()[2] - return int(self.second_of_minute) - if property_name == "second_of_minute_decimal_string": - string = "%f" % (float(self.second_of_minute) - - int(self.second_of_minute)) - string = string.replace("0.", "", 1).rstrip("0") - if not string: - return "0" - return string - if property_name == "time_zone_minute_abs": - return abs(self.time_zone.minutes) - if property_name == "time_zone_hour_abs": - return abs(self.time_zone.hours) - if property_name == "time_zone_sign": - if self.time_zone.hours < 0 or self.time_zone.minutes < 0: - return "-" - return "+" - if property_name == "seconds_since_unix_epoch": - reference_timepoint = TimePoint( - **CALENDAR.UNIX_EPOCH_DATE_TIME_REFERENCE_PROPERTIES) - days, seconds = ( - self - reference_timepoint).get_days_and_seconds() - # N.B. This needs altering if we implement leap seconds. - return str(int(CALENDAR.SECONDS_IN_DAY * days + seconds)) - raise NotImplementedError(property_name) - - def get_second_of_day(self): - """Return the seconds elapsed since the start of the day.""" - second_of_day = 0 - if self.second_of_minute is not None: - second_of_day += self.second_of_minute - if self.minute_of_hour is not None: - second_of_day += self.minute_of_hour * CALENDAR.SECONDS_IN_MINUTE - second_of_day += self.hour_of_day * CALENDAR.SECONDS_IN_HOUR - return second_of_day - - def get_time_zone(self): - """Return the time_zone offset from UTC as a duration.""" - return self.time_zone - - def get_time_zone_utc(self): - """Return whether the time zone is explicitly in UTC.""" - if self.time_zone.unknown: - return False - return self.time_zone.hours == 0 and self.time_zone.minutes == 0 - - def get_week_date(self): - """Return the year, week-of-year, day-of-week for this date.""" - if self.get_is_calendar_date(): - return get_week_date_from_calendar_date(self.year, - self.month_of_year, - self.day_of_month) - if self.get_is_ordinal_date(): - return get_week_date_from_ordinal_date(self.year, - self.day_of_year) - if self.get_is_week_date(): - return self.year, self.week_of_year, self.day_of_week - - def apply_time_zone_offset(self, offset): - """Apply a time zone shift represented by a Duration.""" - if offset.minutes: - if self.minute_of_hour is None: - self.hour_of_day += ( - offset.minutes / float(CALENDAR.MINUTES_IN_HOUR)) - else: - self.minute_of_hour += offset.minutes - self.tick_over() - if offset.hours: - self.hour_of_day += offset.hours - self.tick_over() - - def get_time_zone_offset(self, other): - """Get the difference in hours and minutes between time zones.""" - if other.get_time_zone().unknown or self.get_time_zone().unknown: - return Duration() - return other.get_time_zone() - self.get_time_zone() - - def set_time_zone(self, dest_time_zone): - """Adjust to the new time zone. - - dest_time_zone should be a TimeZone instance expressing difference - from UTC, if any. - - """ - if dest_time_zone.unknown: - return - self.apply_time_zone_offset(dest_time_zone - self.get_time_zone()) - self.time_zone = dest_time_zone - - def set_time_zone_to_local(self): - """Set the time zone to the local time zone, if it's not already.""" - local_hours, local_minutes = timezone.get_local_time_zone() - self.set_time_zone(TimeZone(hours=local_hours, minutes=local_minutes)) - - def set_time_zone_to_utc(self): - """Set the time zone to UTC, if it's not already.""" - self.set_time_zone(TimeZone(hours=0, minutes=0)) - - def to_calendar_date(self): - """Reformat the date in years, month-of-year, day-of-month.""" - year, month, day = self.get_calendar_date() - self.year, self.month_of_year, self.day_of_month = year, month, day - self.day_of_year = None - self.week_of_year = None - self.day_of_week = None - return self - - def to_hour_minute_second(self): - """Expand time fractions into hours, minutes, seconds.""" - hour, minute, second = self.get_hour_minute_second() - self.hour_of_day = hour - self.minute_of_hour = minute - self.second_of_minute = second - - def to_week_date(self): - """Reformat the date in years, week-of-year, day-of-week.""" - self.year, self.week_of_year, self.day_of_week = self.get_week_date() - self.day_of_year = None - self.month_of_year = None - self.day_of_month = None - return self - - def to_ordinal_date(self): - """Reformat the date in years and day-of-the-year.""" - self.year, self.day_of_year = self.get_ordinal_date() - self.month_of_year = None - self.day_of_month = None - self.week_of_year = None - self.day_of_week = None - return self - - def get_largest_truncated_property_name(self): - """Return the largest unit in a truncated representation.""" - if not self.truncated: - return None - prop_dict = self.get_truncated_properties() - for attr in ["year_of_century", "year_of_decade", "month_of_year", - "week_of_year", "day_of_year", "day_of_month", - "day_of_week", "hour_of_day", "minute_of_hour", - "second_of_minute"]: - if attr in prop_dict: - return attr - return None - - def get_smallest_missing_property_name(self): - """Return the smallest unit missing - from a truncated representation.""" - if not self.truncated: - return None - prop_dict = self.get_truncated_properties() - attr_list = (("year_of_century", "century"), - ("year_of_decade", "decade_of_century"), - ("month_of_year", "year_of_century"), - ("week_of_year", "year_of_century"), - ("day_of_year", "year_of_century"), - ("day_of_month", "month_of_year"), - ("day_of_week", "week_of_year"), - ("hour_of_day", "day_of_month"), - ("minute_of_hour", "hour_of_day"), - ("second_of_minute", "minute_of_hour")) - for attr_key, attr_value in attr_list: - if attr_key in prop_dict: - return attr_value - return None - - def get_truncated_properties(self): - """Return a map of properties if this is a truncated representation.""" - if not self.truncated: - return None - props = {} - if self.truncated_property == "year_of_decade": - props.update({"year_of_decade": self.year % 10}) - if self.truncated_property == "year_of_century": - props.update({"year_of_century": self.year % 100}) - for attr in ["month_of_year", "week_of_year", "day_of_year", - "day_of_month", "day_of_week", "hour_of_day", - "minute_of_hour", "second_of_minute"]: - value = getattr(self, attr) - if value is not None: - props.update({attr: value}) - return props - - def add_truncated(self, year_of_century=None, year_of_decade=None, - month_of_year=None, week_of_year=None, day_of_year=None, - day_of_month=None, day_of_week=None, hour_of_day=None, - minute_of_hour=None, second_of_minute=None): - """Combine this TimePoint with truncated time properties.""" - new = self.copy() - if hour_of_day is not None and minute_of_hour is None: - minute_of_hour = 0 - if ((hour_of_day is not None or minute_of_hour is not None) and - second_of_minute is None): - second_of_minute = 0 - if second_of_minute is not None or minute_of_hour is not None: - new.to_hour_minute_second() - if second_of_minute is not None: - while new.second_of_minute != second_of_minute: - new.second_of_minute += 1.0 - new.tick_over() - if minute_of_hour is not None: - while new.minute_of_hour != minute_of_hour: - new.minute_of_hour += 1.0 - new.tick_over() - if hour_of_day is not None: - while new.hour_of_day != hour_of_day: - new.hour_of_day += 1.0 - new.tick_over() - if day_of_week is not None: - new.to_week_date() - while new.day_of_week != day_of_week: - new.day_of_week += 1 - new.tick_over() - if day_of_month is not None: - new.to_calendar_date() - while new.day_of_month != day_of_month: - new.day_of_month += 1 - new.tick_over() - if day_of_year is not None: - new.to_ordinal_date() - while new.day_of_year != day_of_year: - new.day_of_year += 1 - new.tick_over() - if week_of_year is not None: - new.to_week_date() - while new.week_of_year != week_of_year: - new.week_of_year += 1 - new.tick_over() - if month_of_year is not None: - new.to_calendar_date() - while new.month_of_year != month_of_year: - new.month_of_year += 1 - new.tick_over() - if year_of_decade is not None: - new.to_calendar_date() - new_year_of_decade = new.year % 10 - while new_year_of_decade != year_of_decade: - new.year += 1 - new_year_of_decade = new.year % 10 - if year_of_century is not None: - new.to_calendar_date() - new_year_of_century = new.year % 100 - while new_year_of_century != year_of_century: - new.year += 1 - new_year_of_century = new.year % 100 - return new - - def __add__(self, other, no_copy=False): - if isinstance(other, TimePoint): - if self.truncated and not other.truncated: - new = self.copy() - new_other = other.copy() - prev_time_zone = new_other.get_time_zone() - new_other.set_time_zone(new.get_time_zone()) - new_other = new_other.add_truncated( - **new.get_truncated_properties()) - new_other.set_time_zone(prev_time_zone) - return new_other - if other.truncated and not self.truncated: - return other + self - if not isinstance(other, Duration): - raise TypeError( - "Invalid addition: can only add Duration or " - "truncated TimePoint to TimePoint.") - duration = other - if duration.get_is_in_weeks(): - duration = other.copy() - duration.to_days() - if no_copy: - new = self - else: - new = self.copy() - if duration.seconds: - if new.second_of_minute is None: - if new.minute_of_hour is None: - new.hour_of_day += ( - duration.seconds / float(CALENDAR.SECONDS_IN_HOUR)) - else: - new.minute_of_hour += ( - duration.seconds / float(CALENDAR.SECONDS_IN_MINUTE)) - else: - new.second_of_minute += duration.seconds - new.tick_over() - if duration.minutes: - if new.minute_of_hour is None: - new.hour_of_day += ( - duration.minutes / float(CALENDAR.MINUTES_IN_HOUR)) - else: - new.minute_of_hour += duration.minutes - new.tick_over() - if duration.hours: - new.hour_of_day += duration.hours - new.tick_over() - if duration.days: - if new.get_is_calendar_date(): - new.day_of_month += duration.days - elif new.get_is_ordinal_date(): - new.day_of_year += duration.days - else: - new.day_of_week += duration.days - new.tick_over() - if duration.months: - # This is the dangerous one... - new.add_months(duration.months) - if duration.years: - new.year += duration.years - if new.get_is_calendar_date(): - month_index = ( - (new.month_of_year - 1) % CALENDAR.MONTHS_IN_YEAR) - if get_is_leap_year(new.year): - max_day_in_new_month = ( - CALENDAR.DAYS_IN_MONTHS_LEAP[month_index]) - else: - max_day_in_new_month = ( - CALENDAR.DAYS_IN_MONTHS[month_index]) - if new.day_of_month > max_day_in_new_month: - # For example, when Feb 29 - 1 year = Feb 28. - new.day_of_month = max_day_in_new_month - elif new.get_is_ordinal_date(): - max_days_in_year = get_days_in_year(new.year) - if max_days_in_year < new.day_of_year: - new.day_of_year = max_days_in_year - elif new.get_is_week_date(): - max_weeks_in_year = get_weeks_in_year(new.year) - if max_weeks_in_year < new.week_of_year: - new.week_of_year = max_weeks_in_year - return new - - def copy(self): - """Copy this TimePoint without leaving references.""" - dummy_timepoint = TimePoint(is_empty_instance=True) - for attr in self.DATA_ATTRIBUTES: - setattr(dummy_timepoint, attr, getattr(self, attr)) - dummy_timepoint.time_zone = self.time_zone.copy() - return dummy_timepoint - - def get_props(self): - """Return the data properties of this TimePoint.""" - hash_ = [] - for attr in self.DATA_ATTRIBUTES: - value = getattr(self, attr, None) - if callable(getattr(value, "copy", None)): - value = value.copy() - hash_.append((attr, value)) - return hash_ - - def __eq__(self, other: "TimePoint") -> bool: - if other is None: - return False - if self.truncated != other.truncated: - raise TypeError( - "Cannot compare truncated to non-truncated " + - "TimePoint: %s, %s" % (self, other)) - if self.get_props() == other.get_props(): - return True - if self.truncated: - for attribute in self.DATA_ATTRIBUTES: - other_attr = getattr(other, attribute) - self_attr = getattr(self, attribute) - if self_attr != other_attr: - return self_attr == other_attr - return True - other = other.copy() - other.set_time_zone(self.get_time_zone()) - if self.get_is_calendar_date(): - my_date = self.get_calendar_date() - other_date = other.get_calendar_date() - else: - my_date = self.get_ordinal_date() - other_date = other.get_ordinal_date() - my_datetime = list(my_date) + [self.get_second_of_day()] - other_datetime = list(other_date) + [other.get_second_of_day()] - return my_datetime == other_datetime - - def __ne__(self, other: "TimePoint") -> bool: - return not self.__eq__(other) - - def __lt__(self, other: "TimePoint") -> bool: - if other is None: - return False - if self.truncated != other.truncated: - raise TypeError( - "Cannot compare truncated to non-truncated " + - "TimePoint: %s, %s" % (self, other)) - if self.get_props() == other.get_props(): - return False - if self.truncated: - for attribute in self.DATA_ATTRIBUTES: - other_attr = getattr(other, attribute) - self_attr = getattr(self, attribute) - if self_attr != other_attr: - return self_attr < other_attr - return True - other = other.copy() - other.set_time_zone(self.get_time_zone()) - if self.get_is_calendar_date(): - my_date = self.get_calendar_date() - other_date = other.get_calendar_date() - else: - my_date = self.get_ordinal_date() - other_date = other.get_ordinal_date() - my_datetime = list(my_date) + [self.get_second_of_day()] - other_datetime = list(other_date) + [other.get_second_of_day()] - return my_datetime < other_datetime - - def __le__(self, other: "TimePoint") -> bool: - if other is None: - return False - if self.truncated != other.truncated: - raise TypeError( - "Cannot compare truncated to non-truncated " + - "TimePoint: %s, %s" % (self, other)) - if self.get_props() == other.get_props(): - return True - if self.truncated: - for attribute in self.DATA_ATTRIBUTES: - other_attr = getattr(other, attribute) - self_attr = getattr(self, attribute) - if self_attr != other_attr: - return self_attr <= other_attr - return True - other = other.copy() - other.set_time_zone(self.get_time_zone()) - if self.get_is_calendar_date(): - my_date = self.get_calendar_date() - other_date = other.get_calendar_date() - else: - my_date = self.get_ordinal_date() - other_date = other.get_ordinal_date() - my_datetime = list(my_date) + [self.get_second_of_day()] - other_datetime = list(other_date) + [other.get_second_of_day()] - return my_datetime <= other_datetime - - def __gt__(self, other: "TimePoint") -> bool: - if other is None: - return True - if self.truncated != other.truncated: - raise TypeError( - "Cannot compare truncated to non-truncated " + - "TimePoint: %s, %s" % (self, other)) - if self.get_props() == other.get_props(): - return False - if self.truncated: - for attribute in self.DATA_ATTRIBUTES: - other_attr = getattr(other, attribute) - self_attr = getattr(self, attribute) - if self_attr != other_attr: - return self_attr > other_attr - return True - other = other.copy() - other.set_time_zone(self.get_time_zone()) - if self.get_is_calendar_date(): - my_date = self.get_calendar_date() - other_date = other.get_calendar_date() - else: - my_date = self.get_ordinal_date() - other_date = other.get_ordinal_date() - my_datetime = list(my_date) + [self.get_second_of_day()] - other_datetime = list(other_date) + [other.get_second_of_day()] - return my_datetime > other_datetime - - def __ge__(self, other: "TimePoint") -> bool: - if other is None: - return False - if self.truncated != other.truncated: - raise TypeError( - "Cannot compare truncated to non-truncated " + - "TimePoint: %s, %s" % (self, other)) - if self.get_props() == other.get_props(): - return True - if self.truncated: - for attribute in self.DATA_ATTRIBUTES: - other_attr = getattr(other, attribute) - self_attr = getattr(self, attribute) - if self_attr != other_attr: - return self_attr >= other_attr - return True - other = other.copy() - other.set_time_zone(self.get_time_zone()) - if self.get_is_calendar_date(): - my_date = self.get_calendar_date() - other_date = other.get_calendar_date() - else: - my_date = self.get_ordinal_date() - other_date = other.get_ordinal_date() - my_datetime = list(my_date) + [self.get_second_of_day()] - other_datetime = list(other_date) + [other.get_second_of_day()] - return my_datetime >= other_datetime - - def __sub__(self, other): - if isinstance(other, TimePoint): - if other > self: - return -1 * (other - self) - other = other.copy() - other.set_time_zone(self.get_time_zone()) - my_year, my_day_of_year = self.get_ordinal_date() - other_year, other_day_of_year = other.get_ordinal_date() - diff_day = my_day_of_year - other_day_of_year - if my_year > other_year: - diff_day += get_days_in_year_range(other_year, my_year - 1) - else: - diff_day -= get_days_in_year_range(my_year, other_year - 1) - my_hour, my_minute, my_second = self.get_hour_minute_second() - other_hour, other_minute, other_second = ( - other.get_hour_minute_second()) - diff_hour = my_hour - other_hour - diff_minute = my_minute - other_minute - diff_second = my_second - other_second - if diff_second < 0: - diff_minute -= 1 - diff_second += CALENDAR.SECONDS_IN_MINUTE - if diff_minute < 0: - diff_hour -= 1 - diff_minute += CALENDAR.MINUTES_IN_HOUR - if diff_hour < 0: - diff_day -= 1 - diff_hour += CALENDAR.HOURS_IN_DAY - return Duration( - days=diff_day, hours=diff_hour, minutes=diff_minute, - seconds=diff_second) - if not isinstance(other, Duration): - raise TypeError( - "Invalid subtraction type " + - "'%s' - should be Duration." % - type(other).__name__ - ) - duration = other - return self.__add__(duration * -1) - - def add_months(self, num_months): - """Add an amount of months to the representation.""" - if num_months == 0: - return - was_ordinal_date = False - was_week_date = False - if not self.get_is_calendar_date(): - if self.get_is_ordinal_date(): - was_ordinal_date = True - if self.get_is_week_date(): - was_week_date = True - self.to_calendar_date() - for _ in range(abs(num_months)): - if num_months > 0: - self.month_of_year += 1 - if self.month_of_year > CALENDAR.MONTHS_IN_YEAR: - self.month_of_year -= CALENDAR.MONTHS_IN_YEAR - self.year += 1 - if num_months < 0: - self.month_of_year -= 1 - if self.month_of_year < 1: - self.month_of_year += CALENDAR.MONTHS_IN_YEAR - self.year -= 1 - month_index = (self.month_of_year - 1) % CALENDAR.MONTHS_IN_YEAR - if get_is_leap_year(self.year): - max_day_in_new_month = ( - CALENDAR.DAYS_IN_MONTHS_LEAP[month_index]) - else: - max_day_in_new_month = ( - CALENDAR.DAYS_IN_MONTHS[month_index]) - if self.day_of_month > max_day_in_new_month: - # For example, when 31 March + 1 month = 30 April. - self.day_of_month = max_day_in_new_month - self.tick_over() - if was_ordinal_date: - self.to_ordinal_date() - if was_week_date: - self.to_week_date() - - def tick_over(self): - """Correct all the units going from smallest to largest.""" - if (self.hour_of_day is not None and - self.minute_of_hour is not None): - hours_remainder = self.hour_of_day - int(self.hour_of_day) - self.hour_of_day -= hours_remainder - self.minute_of_hour += ( - hours_remainder * CALENDAR.MINUTES_IN_HOUR) - if (self.minute_of_hour is not None and - self.second_of_minute is not None): - minutes_remainder = self.minute_of_hour - int(self.minute_of_hour) - self.minute_of_hour -= minutes_remainder - self.second_of_minute += ( - minutes_remainder * CALENDAR.SECONDS_IN_MINUTE) - if self.second_of_minute is not None: - num_minutes, seconds = divmod(self.second_of_minute, - CALENDAR.SECONDS_IN_MINUTE) - self.minute_of_hour += num_minutes - self.second_of_minute = seconds - if self.minute_of_hour is not None: - num_hours, minutes = divmod(self.minute_of_hour, - CALENDAR.MINUTES_IN_HOUR) - self.hour_of_day += num_hours - self.minute_of_hour = minutes - if self.hour_of_day is not None: - num_days, hours = divmod(self.hour_of_day, CALENDAR.HOURS_IN_DAY) - num_days = int(num_days) - if self.day_of_week is not None: - self.day_of_week += num_days - elif self.day_of_month is not None: - self.day_of_month += num_days - elif self.day_of_year is not None: - self.day_of_year += num_days - self.hour_of_day = hours - if self.day_of_week is not None: - num_weeks, days = divmod( - self.day_of_week - 1, CALENDAR.DAYS_IN_WEEK) - self.week_of_year += num_weeks - self.day_of_week = days + 1 - if self.day_of_month is not None: - self._tick_over_day_of_month() - if self.day_of_year is not None: - while self.day_of_year < 1: - days_in_last_year = get_days_in_year(self.year - 1) - self.day_of_year += days_in_last_year - self.year -= 1 - while self.day_of_year > get_days_in_year(self.year): - days_in_next_year = get_days_in_year(self.year + 1) - self.day_of_year -= days_in_next_year - self.year += 1 - if self.week_of_year is not None: - while self.week_of_year < 1: - weeks_in_last_year = get_weeks_in_year(self.year - 1) - self.week_of_year += weeks_in_last_year - self.year -= 1 - while self.week_of_year > get_weeks_in_year(self.year): - weeks_in_this_year = get_weeks_in_year(self.year) - self.week_of_year -= weeks_in_this_year - self.year += 1 - if self.month_of_year is not None: - while self.month_of_year < 1: - self.month_of_year += CALENDAR.MONTHS_IN_YEAR - self.year -= 1 - while self.month_of_year > CALENDAR.MONTHS_IN_YEAR: - self.month_of_year -= CALENDAR.MONTHS_IN_YEAR - self.year += 1 - - def _tick_over_day_of_month(self): - if self.day_of_month < 1: - num_days = 2 - for month, day in iter_months_days( - self.year, - month_of_year=self.month_of_year, - day_of_month=1, in_reverse=True): - num_days -= 1 - if num_days == self.day_of_month: - self.month_of_year = month - self.day_of_month = day - break - else: - start_year = self.year - month = None - day = None - while num_days != self.day_of_month: - start_year -= 1 - for month, day in iter_months_days( - start_year, in_reverse=True): - num_days -= 1 - if num_days == self.day_of_month: - break - self.year = start_year - self.month_of_year = month - self.day_of_month = day - else: - month_index = (self.month_of_year - 1) % CALENDAR.MONTHS_IN_YEAR - if get_is_leap_year(self.year): - max_day_in_month = CALENDAR.DAYS_IN_MONTHS_LEAP[month_index] - else: - max_day_in_month = CALENDAR.DAYS_IN_MONTHS[month_index] - if self.day_of_month > max_day_in_month: - num_days = 0 - for month, day in iter_months_days( - self.year, - month_of_year=self.month_of_year, - day_of_month=1): - num_days += 1 - if num_days == self.day_of_month: - self.month_of_year = month - self.day_of_month = day - break - else: - start_year = self.year - while num_days != self.day_of_month: - start_year += 1 - for month, day in iter_months_days(start_year): - num_days += 1 - if num_days == self.day_of_month: - self.year = start_year - self.month_of_year = month - self.day_of_month = day - return - - def __str__(self, override_custom_dump_format=False, - strftime_format=None): - if self.expanded_year_digits not in TIMEPOINT_DUMPER_MAP: - TIMEPOINT_DUMPER_MAP[self.expanded_year_digits] = ( - dumpers.TimePointDumper( - self.expanded_year_digits)) - dumper = TIMEPOINT_DUMPER_MAP[self.expanded_year_digits] - if strftime_format is not None: - return dumper.strftime(self, strftime_format) - if self.truncated: - if self.truncated_dump_format and not override_custom_dump_format: - return dumper.dump(self, self.truncated_dump_format) - return dumper.dump(self, self._get_truncated_dump_format()) - if self.dump_format and not override_custom_dump_format: - return dumper.dump(self, self.dump_format) - return dumper.dump(self, self._get_dump_format()) - - def strftime(self, strftime_format): - """Implement equivalent of Python 2's datetime.datetime.strftime. - - Dump based on the format given in the strftime_format string. - - """ - return self.__str__(strftime_format=strftime_format) - - def _get_dump_format(self): - year_digits = 4 + self.expanded_year_digits - year_string = "%0" + str(year_digits) + "d" - if self.expanded_year_digits: - if self.year < 0: - year_string = "-" + year_string % abs(self.year) - else: - year_string = "+" + year_string % abs(self.year) - elif self.year is not None and self.year < 0: - raise OverflowError( - "Year %s can only be represented in expanded format" % - self.year - ) - elif self.year is not None: - year_string = year_string % self.year - - if self.get_is_calendar_date(): - date_string = year_string + "-MM-DD" - elif self.get_is_ordinal_date(): - date_string = year_string + "-DDD" - elif self.get_is_week_date(): - date_string = year_string + "-Www-D" - else: - raise RuntimeError("TimePoint has inconsistent state, points " - "must conform to calendar, ordinal or week " - "dates.") - time_string = "Thh" - if self.minute_of_hour is None: - time_string += ",ii" - else: - time_string += ":mm" - if self.second_of_minute is None: - time_string += ",nn" - else: - seconds_int = int(self.second_of_minute) - time_string += ":ss" - if seconds_int != self.second_of_minute: - time_string += ",tt" - if time_string: - if self.time_zone.hours == 0 and self.time_zone.minutes == 0: - time_string += "Z" - else: - time_string += "+hh:mm" - return date_string + time_string - - def _get_truncated_dump_format(self): - year_string = "-" - if self.truncated_property == "year_of_decade": - year_string = "-" + "z" - elif self.truncated_property == "year_of_century": - if self.day_of_month is None and self.month_of_year is not None: - year_string = "-YY" - else: - year_string = "YY" - date_string = year_string - if self.month_of_year is not None: - date_string = year_string + "-MM" - if self.day_of_month is not None: - date_string += "-DD" - elif self.day_of_month is not None: - if year_string == "-": - date_string = year_string + "--DD" - else: - date_string = year_string + "-DD" - if self.day_of_year is not None: - day_string = "DDD" - if year_string == "-": - date_string = year_string + day_string - else: - date_string = year_string + "-" + day_string - if self.week_of_year is not None: - if year_string == "-": - date_string = year_string + "Www" - else: - date_string = year_string + "-Www" - if self.day_of_week is not None: - date_string += "-D" - elif self.day_of_week is not None: - if year_string == "-": - date_string = year_string + "W-D" - else: - date_string = year_string + "-W-D" - time_string = "" - if (self.hour_of_day is None and - (self.minute_of_hour is not None or - self.second_of_minute is not None)): - time_string = "T-" - elif (self.hour_of_day is not None and - int(self.hour_of_day) != self.hour_of_day): - time_string = "Thh,ii" - elif self.hour_of_day is not None: - time_string = "Thh" - if self.minute_of_hour is None and self.second_of_minute is not None: - time_string += "-" - elif (self.minute_of_hour is not None and - int(self.minute_of_hour) != self.minute_of_hour): - if self.hour_of_day is not None: - time_string += ":" - time_string += "mm,nn" - elif self.minute_of_hour is not None: - if self.hour_of_day is not None: - time_string += ":" - time_string += "mm" - if self.second_of_minute is not None: - seconds_int = int(self.second_of_minute) - if self.minute_of_hour is not None: - time_string += ":" - time_string += "ss" - if seconds_int != self.second_of_minute: - time_string += ",tt" - if time_string: - if self.time_zone.hours == 0 and self.time_zone.minutes == 0: - time_string += "Z" - else: - time_string += "+hh:mm" - if date_string == "YY": - date_string = "-YY" - time_string = time_string.replace(":", "") - if date_string == "-": - date_string = "" - return date_string + time_string - - __repr__ = __str__ - - -def _format_remainder(float_time_number): - """Format a floating point remainder of a time unit.""" - string = "," + ("%f" % float_time_number)[2:].rstrip("0") - if string == ",": - return "" - return string - - -@lru_cache(maxsize=100000) -def get_is_leap_year(year): - """Return if year is a leap year.""" - year_is_leap = False - for factor, is_leap_factor in CALENDAR.LEAP_YEAR_FACTOR_TRUTHS: - if year % factor == 0: - year_is_leap = is_leap_factor - return year_is_leap - - -def get_days_in_year_range(start_year, end_year): - """Return the number of days within this year range (inclusive).""" - return _get_days_in_year_range(start_year, end_year, CALENDAR.mode) - - -@lru_cache(maxsize=100000) -def _get_days_in_year_range(start_year, end_year, _): - """Return the number of days within this year range (inclusive). - - If end_year > start_year, return the days in start_year plus - the days in the intervening years before end_year, plus the - days in end_year. - - If end_year == start_year, return the days in start_year. - - If end_year < start_year, return 0. - - """ - # Get the number of days discounting leap years. - if start_year == end_year: - return get_days_in_year(start_year) - if start_year > end_year: - return 0 - days = (end_year + 1 - start_year) * CALENDAR.DAYS_IN_YEAR - diff_days_leap = (CALENDAR.DAYS_IN_YEAR_LEAP - CALENDAR.DAYS_IN_YEAR) - for factor, is_leap_factor in CALENDAR.LEAP_YEAR_FACTOR_TRUTHS: - num_corrections = 0 - if start_year % factor == 0: - num_corrections += 1 - if end_year != start_year and end_year % factor == 0: - num_corrections += 1 - factor_start_year = start_year + 1 - while (factor_start_year % factor != 0 and - factor_start_year < end_year): - factor_start_year += 1 - if factor_start_year < end_year: - num_corrections += 1 - num_corrections += ( - end_year - (factor_start_year + 1)) // factor - if is_leap_factor: - days += num_corrections * diff_days_leap - else: - days -= num_corrections * diff_days_leap - return days - - -def get_days_in_year(year): - """Return the number of days in this particular year.""" - return _get_days_in_year(year, CALENDAR.mode) - - -@lru_cache(maxsize=100000) -def _get_days_in_year(year, _): - """Return the number of days in this particular year.""" - if get_is_leap_year(year): - return CALENDAR.DAYS_IN_YEAR_LEAP - return CALENDAR.DAYS_IN_YEAR - - -def get_weeks_in_year(year): - """Return the number of calendar weeks in this week date year.""" - return _get_weeks_in_year(year, CALENDAR.mode) - - -@lru_cache(maxsize=100000) -def _get_weeks_in_year(year, _): - """Return the number of calendar weeks in this week date year.""" - cal_year, cal_ord_days = get_ordinal_date_week_date_start(year) - cal_year_next, cal_ord_days_next = get_ordinal_date_week_date_start( - year + 1) - diff_days = cal_ord_days_next - cal_ord_days - for intervening_year in range(cal_year, cal_year_next): - diff_days += get_days_in_year(intervening_year) - return diff_days // CALENDAR.DAYS_IN_WEEK - - -def get_calendar_date_from_ordinal_date(year, day_of_year): - """Translate an ordinal date into a calendar date. - - Returns the calendar year, calendar month, calendar day-of-month. - - Arguments: - year is an integer that denotes the ordinal date year - day_of_year is an integer that denotes the ordinal day in the year. - - """ - iter_num_days = 0 - for iter_month, iter_day in iter_months_days(year): - iter_num_days += 1 - if iter_num_days == day_of_year: - return year, iter_month, iter_day - raise ValueError("Bad ordinal date: %s-%03d" % (year, day_of_year)) - - -def get_calendar_date_from_week_date(year, week_of_year, day_of_week): - """Translate a week date into a calendar date. - - Returns the calendar year, calendar month, calendar day-of-month. - - Arguments: - year is an integer that denotes the week date year (may differ - from calendar year) - week_of_year is an integer that denotes the week number in the year - day_of_week is an integer that denotes the day of the week (1-7). - - """ - num_days_week_year = ( - (week_of_year - 1) * CALENDAR.DAYS_IN_WEEK + day_of_week - 1) - start_year, start_month, start_day = ( - get_calendar_date_week_date_start(year)) - if num_days_week_year == 0: - return start_year, start_month, start_day - total_iter_days = 0 - # Loop over the months and days left in the start year. - for iter_month, iter_day in iter_months_days( - start_year, month_of_year=start_month, - day_of_month=start_day + 1): - total_iter_days += 1 - if num_days_week_year == total_iter_days: - return start_year, iter_month, iter_day - if start_year < year: - # We've only looped over the last year - now the current one. - for iter_month, iter_day in iter_months_days(year): - total_iter_days += 1 - if num_days_week_year == total_iter_days: - return year, iter_month, iter_day - for iter_month, iter_day in iter_months_days(year + 1): - # Loop over the following year. - total_iter_days += 1 - if num_days_week_year == total_iter_days: - return year + 1, iter_month, iter_day - raise ValueError("Bad week date: %s-W%02d-%s" % (year, - week_of_year, - day_of_week)) - - -def get_ordinal_date_from_calendar_date(year, month_of_year, day_of_month): - """Translate a calendar date into an ordinal date. - - Returns the ordinal year, calendar month, calendar day-of-month. - - Arguments: - year is an integer that denotes the year - month_of_year is an integer that denotes the month number in the - year. - day_of_month is an integer that denotes the day number in the - month_of_year. - - """ - iter_num_days = 0 - for iter_month, iter_day in iter_months_days(year): - iter_num_days += 1 - if iter_month == month_of_year and iter_day == day_of_month: - return year, iter_num_days - raise ValueError("Bad calendar date: %s-%02d-%02d" % (year, - month_of_year, - day_of_month)) - - -def get_ordinal_date_from_week_date(year, week_of_year, day_of_week): - """Translate a week date into an ordinal date. - - Returns the ordinal year, ordinal day-of-year. - - Arguments: - year is an integer that denotes the week date year (which may - differ from the ordinal or calendar year) - week_of_year is an integer that denotes the week number in the - year. - day_of_week is an integer that denotes the day number in the - week_of_year. - - """ - cal_year, cal_month, cal_day_of_month = get_calendar_date_from_week_date( - year, week_of_year, day_of_week) - return get_ordinal_date_from_calendar_date( - cal_year, cal_month, cal_day_of_month) - - -def get_week_date_from_calendar_date(year, month_of_year, day_of_month): - """Translate a calendar date into an week date. - - Returns the week date year, week-of-year, day-of-week. - - Arguments: - year is an integer that denotes the calendar year, which may - differ from the week date year. - month_of_year is an integer that denotes the month number in the - above year. - day_of_month is an integer that denotes the day number in the - above month_of_year. - - """ - prev_start = get_calendar_date_week_date_start(year - 1) - this_start = get_calendar_date_week_date_start(year) - next_start = get_calendar_date_week_date_start(year + 1) - - cal_date = (year, month_of_year, day_of_month) - - if prev_start <= cal_date < this_start: - # This calendar date is in the previous week date year. - start_year, start_month, start_day = prev_start - week_date_start_year = year - 1 - elif this_start <= cal_date < next_start: - # This calendar date is in the same week date year. - start_year, start_month, start_day = this_start - week_date_start_year = year - else: - # This calendar date is in the next week date year. - start_year, start_month, start_day = next_start - week_date_start_year = year + 1 - - total_iter_days = -1 - # A week date year can theoretically span 3 calendar years... - for iter_month, iter_day in iter_months_days(start_year, - month_of_year=start_month, - day_of_month=start_day): - total_iter_days += 1 - if (start_year == year and - iter_month == month_of_year and - iter_day == day_of_month): - week_of_year = (total_iter_days // CALENDAR.DAYS_IN_WEEK) + 1 - day_of_week = (total_iter_days % CALENDAR.DAYS_IN_WEEK) + 1 - return week_date_start_year, week_of_year, day_of_week - - for iter_start_year in [start_year + 1, start_year + 2]: - # Look at following year when the calendar date is e.g. very early Jan. - for iter_month, iter_day in iter_months_days(iter_start_year): - total_iter_days += 1 - if (iter_start_year == year and - iter_month == month_of_year and - iter_day == day_of_month): - week_of_year = (total_iter_days // CALENDAR.DAYS_IN_WEEK) + 1 - day_of_week = (total_iter_days % CALENDAR.DAYS_IN_WEEK) + 1 - return week_date_start_year, week_of_year, day_of_week - raise ValueError("Bad calendar date: %s-%02d-%02d" % (year, - month_of_year, - day_of_month)) - - -def get_week_date_from_ordinal_date(year, day_of_year): - """Translate an ordinal date into a week date. - - Returns the week date year, week-of-year, day-of-week. - - Arguments: - year is an integer that denotes the ordinal date year, which - may differ from the week date year. - day_of_year is an integer that denotes the ordinal day in the year. - - """ - year, month, day = get_calendar_date_from_ordinal_date(year, day_of_year) - return get_week_date_from_calendar_date(year, month, day) - - -def get_calendar_date_week_date_start(year): - """Return the calendar date of the start of (week date) year.""" - return _get_calendar_date_week_date_start(year, CALENDAR.mode) - - -@lru_cache(maxsize=100000) -def _get_calendar_date_week_date_start(year, _): - """Return the calendar date of the start of (week date) year.""" - ref_year, ref_month, ref_day = ( - CALENDAR.WEEK_DAY_START_REFERENCE["calendar"]) - ref_year, ref_ordinal_day = ( - CALENDAR.WEEK_DAY_START_REFERENCE["ordinal"]) - if year == ref_year: - return ref_year, ref_month, ref_day - # Calculate the weekday for 1 January in this calendar year. - days_diff = 0 - if year > ref_year: - days_diff = 1 - ref_ordinal_day - days_diff += get_days_in_year_range(ref_year, year - 1) - elif ref_year > year: - days_diff = ref_ordinal_day - 2 - days_diff += get_days_in_year_range(year, ref_year - 1) - - weekdays_diff = days_diff % CALENDAR.DAYS_IN_WEEK - if year > ref_year: - day_of_week_start_year = weekdays_diff + 1 - else: - # Jan 1 as day of week. - day_of_week_start_year = CALENDAR.DAYS_IN_WEEK - weekdays_diff - if day_of_week_start_year == 1: - return year, 1, 1 - if day_of_week_start_year > 4: - # This week belongs to the previous year; get the next Monday. - day = 1 + (8 - day_of_week_start_year) - return year, 1, day - # The week starts in the previous year - get the previous Monday. - for month, day in iter_months_days(year - 1, in_reverse=True): - day_of_week_start_year -= 1 - if day_of_week_start_year == 1: - return year - 1, month, day - - -def get_days_since_1_ad(year): - """Return the number of days since Jan 1, 1 A.D. to the year end.""" - return _get_days_since_1_ad(year, CALENDAR.mode) - - -@lru_cache(maxsize=100000) -def _get_days_since_1_ad(year, _): - """Return the number of days since Jan 1, 1 A.D. to the year end.""" - if year == 1: - return get_days_in_year(year) - elif year < 1: - return 0 - return get_days_in_year_range(1, year) - - -def get_ordinal_date_week_date_start(year): - """Return the ordinal week date start for year (year, day-of-year).""" - return _get_ordinal_date_week_date_start(year, CALENDAR.mode) - - -@lru_cache(maxsize=100000) -def _get_ordinal_date_week_date_start(year, _): - """Return the ordinal week date start for year (year, day-of-year).""" - cal_year, cal_month, cal_day = get_calendar_date_week_date_start(year) - total_days = 0 - for iter_month, iter_day in iter_months_days(cal_year): - total_days += 1 - if iter_month == cal_month and iter_day == cal_day: - return cal_year, total_days - - -def get_timepoint_for_now(): - """Return a TimePoint at the current date/time.""" - import time - return get_timepoint_from_seconds_since_unix_epoch(time.time()) - - -def get_timepoint_from_seconds_since_unix_epoch(num_seconds): - """Return a TimePoint at a date/time specified in Unix time. - - Note that Unix time always counts 1 day = 86400 seconds, so if - we implement leap seconds we need to make the distinction. - - """ - reference_timepoint = TimePoint( - **CALENDAR.UNIX_EPOCH_DATE_TIME_REFERENCE_PROPERTIES) - return reference_timepoint + Duration(seconds=float(num_seconds)) - - -def get_timepoint_properties_from_seconds_since_unix_epoch(num_seconds): - """Translate Unix time into a dict of TimePoint constructor properties.""" - properties = dict( - get_timepoint_from_seconds_since_unix_epoch(num_seconds).get_props()) - time_zone = properties.pop("time_zone") - properties["time_zone_hour"] = time_zone.hours - properties["time_zone_minute"] = time_zone.minutes - return properties - - -def iter_months_days(year, month_of_year=None, day_of_month=None, - in_reverse=False): - """Iterate over each day in each month of year. - - year is an integer specifying the year to use. - month_of_year is an optional integer, specifying a start month. - day_of_month is an optional integer, specifying a start day. - in_reverse is an optional boolean that reverses the iteration if - True (default False). - - """ - is_leap_year = get_is_leap_year(year) - return _iter_months_days( - is_leap_year, month_of_year, day_of_month, CALENDAR.mode, in_reverse) - - -@lru_cache(maxsize=100000) -def _iter_months_days(is_leap_year, month_of_year, day_of_month, _, - in_reverse=False): - if day_of_month is not None and month_of_year is None: - raise ValueError("Need to specify start month as well as day.") - source = CALENDAR.INDEXED_DAYS_IN_MONTHS - if is_leap_year: - source = CALENDAR.INDEXED_DAYS_IN_MONTHS_LEAP - results = [] - if in_reverse: - if month_of_year is None: - for month_num, days in reversed(source): - day_range = range(days, 0, -1) - for day in day_range: - results.append((month_num, day)) - else: - for month_num, days in reversed(source): - if month_num > month_of_year: - continue - elif month_num == month_of_year and day_of_month is not None: - day_range = range(day_of_month, 0, -1) - else: - day_range = range(days, 0, -1) - for day in day_range: - results.append((month_num, day)) - else: - if month_of_year is None: - for month_num, days in source: - day_range = range(1, days + 1) - for day in day_range: - results.append((month_num, day)) - else: - for month_num, days in source[month_of_year - 1:]: - if month_num == month_of_year and day_of_month is not None: - day_range = range(day_of_month, days + 1) - else: - day_range = range(1, days + 1) - for day in day_range: - results.append((month_num, day)) - return results - - -def _int_caster(number, name="number", allow_none=False): - if allow_none and number is None: - return None - try: - int_number = int(number) - float_number = float(number) - except (TypeError, ValueError) as num_exc: - raise BadInputError( - BadInputError.INT_CAST, name, number, num_exc) - if float(int_number) != float_number: - raise BadInputError( - BadInputError.INT_REMAINDER, name, number) - return int_number - - -def _type_checker(*objects): - for type_info in objects: - value, name = type_info[:2] - allowed_types = list(type_info[2:]) - none_is_allowed = False - if None in allowed_types: - if value is None: - break - none_is_allowed = True - allowed_types.remove(None) - allowed_types.append(type(None)) - if allowed_types and isinstance(value, allowed_types[0]): - break - if int in allowed_types and float not in allowed_types: - value = _int_caster(value, name=name, allow_none=none_is_allowed) - if any(isinstance(value, type_) for type_ in allowed_types): - break - values_string = "" - if allowed_types: - values_string = " should be: " - values_string += " or ".join(str(v) for v in allowed_types) - raise BadInputError( - BadInputError.TYPE, name, repr(value), values_string) - - -PARSE_PROPERTY_TRANSLATORS = { - "seconds_since_unix_epoch": - get_timepoint_properties_from_seconds_since_unix_epoch -} diff --git a/lib/python/isodatetime/datetimeoper.py b/lib/python/isodatetime/datetimeoper.py deleted file mode 100644 index 09ed19ebbe..0000000000 --- a/lib/python/isodatetime/datetimeoper.py +++ /dev/null @@ -1,341 +0,0 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- -# Copyright (C) 2013-2019 British Crown (Met Office) & Contributors. -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program. If not, see . -# ----------------------------------------------------------------------------- -"""High level common date-time point and duration utilities.""" - -from datetime import datetime -import os -from .data import Calendar, get_timepoint_for_now -from .dumpers import TimePointDumper -from .parsers import TimePointParser, DurationParser, TimeRecurrenceParser - - -class OffsetValueError(ValueError): - - """Bad offset value.""" - - def __str__(self): - return "%s: bad offset value" % self.args[0] - - -class DateTimeOperator(object): - - """A class to parse and print date string with an offset.""" - - CURRENT_TIME_DUMP_FORMAT = "CCYY-MM-DDThh:mm:ss+hh:mm" - CURRENT_TIME_DUMP_FORMAT_Z = "CCYY-MM-DDThh:mm:ssZ" - - ENV_REF = "ISODATETIMEREF" - ENV_CALENDAR_MODE = "ISODATETIMECALENDAR" - - # strptime formats and their compatibility with the ISO 8601 parser. - PARSE_FORMATS = [ - "%a %b %d %H:%M:%S %Y", # ctime - "%a %d %b %H:%M:%S %Z %Y", # Unix "date" - "%Y-%m-%dT%H:%M:%S", # ISO8601, extended - "%Y%m%dT%H%M%S", # ISO8601, basic - ] - - STR_NOW = "now" - STR_REF = "ref" - - UNITS = { - "w": "weeks", - "d": "days", - "h": "hours", - "m": "minutes", - "s": "seconds", - } - - def __init__( - self, - parse_format=None, - utc_mode=False, - calendar_mode=None, - ref_point_str=None, - ): - """Constructor. - - parse_format -- If specified, parse with the specified format. - Otherwise, parse with one of the format strings in - self.PARSE_FORMATS. The format should be a string - compatible to strptime(3). - - utc_mode -- If True, parse/print in UTC mode rather than local or - other timezones. - - calendar_mode -- Set calendar mode, for isodatetime.data.Calendar. - - ref_point_str -- Set the reference time point for operations. - If not specified, operations use current date time. - - """ - self.parse_formats = self.PARSE_FORMATS - self.custom_parse_format = parse_format - self.utc_mode = utc_mode - if self.utc_mode: - assumed_time_zone = (0, 0) - else: - assumed_time_zone = None - - if not calendar_mode: - calendar_mode = os.getenv(self.ENV_CALENDAR_MODE) - self.set_calendar_mode(calendar_mode) - - self.time_point_dumper = TimePointDumper() - self.time_point_parser = TimePointParser( - assumed_time_zone=assumed_time_zone) - self.duration_parser = DurationParser() - self.recurrence_parser = TimeRecurrenceParser( - self.time_point_parser, self.duration_parser) - - if ref_point_str is None: - self.ref_point_str = os.getenv(self.ENV_REF) - else: - self.ref_point_str = ref_point_str - - def date_format(self, print_format, time_point): - """Reformat time_point according to print_format. - - time_point -- The time point to format. - - """ - if "%" in print_format: - return self.strftime(time_point, print_format) - return self.time_point_dumper.dump(time_point, print_format) - - def date_parse(self, time_point_str=None): - """Parse time_point_str. - - Return (t, format) where t is a isodatetime.data.TimePoint object and - format is the format that matches time_point_str. - - time_point_str -- The time point string to parse. - Otherwise, use ref time. - - """ - if time_point_str == self.STR_REF: - time_point_str = self.ref_point_str - if time_point_str is None or time_point_str == self.STR_NOW: - time_point = get_timepoint_for_now() - time_point.set_time_zone_to_local() - if self.utc_mode or time_point.get_time_zone_utc(): # is in UTC - parse_format = self.CURRENT_TIME_DUMP_FORMAT_Z - else: - parse_format = self.CURRENT_TIME_DUMP_FORMAT - elif self.custom_parse_format is not None: - parse_format = self.custom_parse_format - time_point = self.strptime(time_point_str, parse_format) - else: - time_point = None - for parse_format in self.parse_formats: - try: - time_point = self.strptime(time_point_str, parse_format) - break - except ValueError: - pass - if time_point is None: - time_point = self.time_point_parser.parse( - time_point_str, - dump_as_parsed=True) - parse_format = time_point.dump_format - if self.utc_mode: - time_point.set_time_zone_to_utc() - return time_point, parse_format - - def date_shift(self, time_point, offset=None): - """Return a date string with an offset. - - time_point -- A time point or time point string. - Otherwise, use current time. - - offset -- If specified, it should be a string containing the offset - that has the format "[+/-]nU[nU...]" where "n" is an - integer, and U is a unit matching a key in self.UNITS. - - """ - # Offset - if offset: - sign = "+" - if offset.startswith("-") or offset.startswith("+"): - sign = offset[0] - offset = offset[1:] - # Parse and apply. - try: - duration = self.duration_parser.parse(offset) - except ValueError: - raise OffsetValueError(offset) - if sign == "-": - time_point -= duration - else: - time_point += duration - - return time_point - - @staticmethod - def date_diff(time_point_1=None, time_point_2=None): - """Return (duration, is_negative) between two TimePoint objects. - - duration -- is a Duration instance. - is_negative -- is "-" if time_point_2 is in the past of time_point_1. - """ - if time_point_2 < time_point_1: - return (time_point_1 - time_point_2, "-") - else: - return (time_point_2 - time_point_1, "") - - @staticmethod - def date_diff_format(print_format, duration, sign): - """Format a duration.""" - if print_format: - delta_lookup = { - "y": duration.years, - "m": duration.months, - "d": duration.days, - "h": duration.hours, - "M": duration.minutes, - "s": duration.seconds, - } - expression = "" - for item in print_format: - if item not in delta_lookup: - expression += item - elif float(delta_lookup[item]).is_integer(): - expression += str(int(delta_lookup[item])) - else: - expression += str(delta_lookup[item]) - return sign + expression - else: - return sign + str(duration) - - @staticmethod - def get_calendar_mode(): - """Get current calendar mode.""" - return Calendar.default().mode - - @staticmethod - def set_calendar_mode(calendar_mode): - """Set calendar mode for subsequent operations. - - Raise KeyError if calendar_mode is invalid. - - """ - Calendar.default().set_mode(calendar_mode) - - def strftime(self, time_point, print_format): - """Use either the isodatetime or datetime strftime time formatting.""" - try: - return time_point.strftime(print_format) - except ValueError: - return self.get_datetime_strftime(time_point, print_format) - - def strptime(self, time_point_str, parse_format): - """Use either the isodatetime or datetime strptime time parsing.""" - try: - return self.time_point_parser.strptime( - time_point_str, parse_format) - except ValueError: - return self.get_datetime_strptime(time_point_str, parse_format) - - @staticmethod - def get_datetime_strftime(time_point, print_format): - """Use the datetime library's strftime as a fallback.""" - calendar_date = time_point.copy().to_calendar_date() - year, month, day = calendar_date.get_calendar_date() - hour, minute, second = time_point.get_hour_minute_second() - microsecond = int(1.0e6 * (second - int(second))) - hour = int(hour) - minute = int(minute) - second = int(second) - date_time = datetime( - year, month, day, hour, minute, second, microsecond) - return date_time.strftime(print_format) - - def get_datetime_strptime(self, time_point_str, parse_format): - """Use the datetime library's strptime as a fallback.""" - date_time = datetime.strptime(time_point_str, parse_format) - return self.time_point_parser.parse(date_time.isoformat()) - - def process_time_point_str( - self, - time_point_str=None, - offsets=None, - print_format=None, - ): - """Process time point string with optional offsets.""" - time_point, parse_format = self.date_parse(time_point_str) - if offsets: - for offset in offsets: - time_point = self.date_shift(time_point, offset) - if print_format: - return self.date_format(print_format, time_point) - else: - return self.date_format(parse_format, time_point) - - def diff_time_point_strs( - self, - time_point_str1, - time_point_str2, - offsets1=None, - offsets2=None, - print_format=None, - duration_print_format=None, - ): - """Calculate duration between 2 time point strings. - - Each time point string may have optional offsets. - """ - time_point1 = self.date_parse(time_point_str1)[0] - time_point2 = self.date_parse(time_point_str2)[0] - if offsets1: - for offset in offsets1: - time_point1 = self.date_shift(time_point1, offset) - if offsets2: - for offset in offsets2: - time_point2 = self.date_shift(time_point2, offset) - duration, sign = self.date_diff(time_point1, time_point2) - out = self.date_diff_format(print_format, duration, sign) - if duration_print_format: - return self.format_duration_str(out, duration_print_format) - else: - return out - - def format_duration_str(self, duration_str, duration_print_format): - """Parse duration string, return as total of a unit. - - Unit can be H, M or S (for hours, minutes or seconds). - """ - duration = self.duration_parser.parse( - duration_str.replace('\\', '')) # allows negative durations - time = duration.get_seconds() - options = {'S': time, 'M': time / 60, 'H': time / 3600} - if duration_print_format.upper() in options: - # supplied duration format is valid - # (upper removes case-sensitivity) - return options[duration_print_format.upper()] - else: - # supplied duration format not valid - raise ValueError( - 'Invalid duration print format, ' - 'should use one of H, M, S for (hours, minutes, seconds)' - ) - - def iter_recurrence_str(self, recurrence_str, print_format=None): - """Parse recurrence string, return time point strings iterator.""" - recurrence = self.recurrence_parser.parse(recurrence_str) - for time_point in recurrence: - yield self.strftime(time_point, print_format) diff --git a/lib/python/isodatetime/dumpers.py b/lib/python/isodatetime/dumpers.py deleted file mode 100644 index ecf97b4700..0000000000 --- a/lib/python/isodatetime/dumpers.py +++ /dev/null @@ -1,235 +0,0 @@ -# -*- coding: utf-8 -*- -# ---------------------------------------------------------------------------- -# Copyright (C) 2013-2019 British Crown (Met Office) & Contributors. -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program. If not, see . -# ---------------------------------------------------------------------------- - -"""This provides data model dumping functionality.""" - -import re - -from . import parser_spec - -from functools import lru_cache - - -class TimePointDumperBoundsError(ValueError): - - """An error raised when a TimePoint can't be dumped within bounds.""" - - MESSAGE = "Cannot dump TimePoint {0}: {1} not in bounds {2} to {3}." - - def __str__(self): - return self.MESSAGE.format(*self.args) - - -class TimePointDumper(object): - - """Dump TimePoint instances to strings using particular formats. - - A format can be specified in the self.dump method via the - formatting_string argument. Unlike Python's datetime strftime - method, this uses normal/Unicode character patterns to represent - which pieces of information to output where. A full reference - of valid patterns is found in the parser_spec module, with lots - of examples (coincidentally, used to generate the parsing). - Anything not matched will get left as it is in the string. - Specifying a particular time zone will result in a time zone - conversion of the date/time information before it is output. - - For example, the following formatting_string - 'CCYYMMDDThhmmZ' is made up of: - CC - year (century) information, e.g. 19 - YY - year (decade, year of decade) information, e.g. 85 - MM - month of year information, e.g. 05 - DD - day of month information, e.g. 31 - T - left alone, date/time separator - hh - hour of day information, e.g. 06 - mm - minute of hour information, e.g. 58 - Z - Zulu or UTC zero-offset time zone, left in, forces time zone - conversion - and might dump a TimePoint instance like this: '19850531T0658Z'. - - Keyword arguments: - num_expanded_year_digits - an integer (default 2) that indicates - how many extra year digits to apply if appropriate (and if the - user requests that information). - - """ - - def __init__(self, num_expanded_year_digits=2): - self._timepoint_parser = None - self._rec_formats = {"date": [], "time": [], "time_zone": []} - self._time_designator = parser_spec.TIME_DESIGNATOR - self.num_expanded_year_digits = num_expanded_year_digits - for info, key in [ - (parser_spec.get_date_translate_info( - num_expanded_year_digits), - "date"), - (parser_spec.get_time_translate_info(), "time"), - (parser_spec.get_time_zone_translate_info(), "time_zone")]: - for regex, _, format_sub, prop_name in info: - rec = re.compile(regex) - self._rec_formats[key].append((rec, format_sub, prop_name)) - - def dump(self, timepoint, formatting_string): - """Dump a timepoint according to formatting_string. - - The syntax for formatting_string is the syntax used for the - TimePointParser internals. See TimePointParser.*_TRANSLATE_INFO. - - """ - if "%" in formatting_string: - try: - return self.strftime(timepoint, formatting_string) - except TimePointDumperBoundsError: - raise - except ValueError: - pass - expression, properties, custom_time_zone = ( - self._get_expression_and_properties(formatting_string)) - return self._dump_expression_with_properties( - timepoint, expression, properties, - custom_time_zone=custom_time_zone - ) - - def strftime(self, timepoint, formatting_string): - """Implement equivalent of Python 2's datetime.datetime.strftime. - - Dump timepoint based on the format given in formatting_string. - - """ - split_format = parser_spec.REC_SPLIT_STRFTIME_DIRECTIVE.split( - formatting_string) - expression = "" - properties = [] - for item in split_format: - if parser_spec.REC_STRFTIME_DIRECTIVE_TOKEN.search(item): - item_expression, item_properties = ( - parser_spec.translate_strftime_token(item)) - expression += item_expression - properties += item_properties - else: - expression += item - return self._dump_expression_with_properties( - timepoint, expression, properties) - - def _dump_expression_with_properties(self, timepoint, expression, - properties, custom_time_zone=None): - if not timepoint.truncated: - if "week_of_year" in properties or "day_of_week" in properties: - if not ("month_of_year" in properties or - "day_of_month" in properties or - "day_of_year" in properties): - # We need the year to be in week years. - timepoint = timepoint.copy().to_week_date() - elif (timepoint.get_is_week_date() and ( - "month_of_year" in properties or - "day_of_month" in properties or - "day_of_year" in properties)): - # We need the year to be in standard calendar years. - timepoint = timepoint.copy().to_calendar_date() - - if custom_time_zone is not None: - timepoint = timepoint.copy() - if custom_time_zone == (0, 0): - timepoint.set_time_zone_to_utc() - else: - current_time_zone = timepoint.get_time_zone() - new_time_zone = current_time_zone.copy() - new_time_zone.hours = custom_time_zone[0] - new_time_zone.minutes = custom_time_zone[1] - new_time_zone.unknown = False - timepoint.set_time_zone(new_time_zone) - property_map = {} - for property_ in properties: - property_map[property_] = timepoint.get(property_) - if (property_ == "century" and - ("expanded_year_digits" not in properties or - not self.num_expanded_year_digits)): - min_value = 0 - max_value = 9999 - elif property_ == "expanded_year_digits": - max_value = (10 ** (self.num_expanded_year_digits + 4)) - 1 - min_value = -max_value - else: - continue - value = timepoint.year - if not (min_value <= value <= max_value): - raise TimePointDumperBoundsError( - "year", value, min_value, max_value) - return expression % property_map - - @lru_cache(maxsize=100000) - def _get_expression_and_properties(self, formatting_string): - date_time_strings = formatting_string.split( - self._time_designator) - date_string = date_time_strings[0] - time_string = "" - time_zone_string = "" - custom_time_zone = None - if len(date_time_strings) > 1: - time_string = date_time_strings[1] - if time_string.endswith("Z"): - time_string = time_string[:-1] - time_zone_string = "Z" - custom_time_zone = (0, 0) - elif "+hh" in time_string: - time_string, time_zone_string = time_string.split("+") - time_zone_string = "+" + time_zone_string - elif "+" in time_string: - time_string, time_zone_string = time_string.split("+") - time_zone_string = "+" + time_zone_string - custom_time_zone = self.get_time_zone(time_zone_string) - elif "-" in time_string.lstrip("-"): - time_string, time_zone_string = time_string.split("-") - time_zone_string = "-" + time_zone_string - custom_time_zone = self.get_time_zone(time_zone_string) - point_prop_list = [] - string_map = {"date": "", "time": "", "time_zone": ""} - for string, key in [(date_string, "date"), - (time_string, "time"), - (time_zone_string, "time_zone")]: - for rec, format_sub, prop in self._rec_formats[key]: - new_string = rec.sub(format_sub, string) - if new_string != string and prop is not None: - point_prop_list.append(prop) - string = new_string - string_map[key] = string - expression = string_map["date"] - if string_map["time"]: - expression += self._time_designator + string_map["time"] - expression += string_map["time_zone"] - return expression, tuple(point_prop_list), custom_time_zone - - @lru_cache(maxsize=100000) - def get_time_zone(self, time_zone_string): - """Parse and return time zone from time_zone_string.""" - from . import parsers - if self._timepoint_parser is None: - self._timepoint_parser = parsers.TimePointParser() - try: - info = self._timepoint_parser.get_time_zone_info( - time_zone_string)[1] - except parsers.ISO8601SyntaxError: - return None - info = self._timepoint_parser.process_time_zone_info(info) - if info.get('time_zone_utc'): - return 0, 0 - if "time_zone_hour" not in info and "time_zone_minute" not in info: - return None - hour = int(info.get("time_zone_hour", 0)) - minute = int(info.get("time_zone_minute", 0)) - return hour, minute diff --git a/lib/python/isodatetime/main.py b/lib/python/isodatetime/main.py deleted file mode 100644 index f72d7b98e5..0000000000 --- a/lib/python/isodatetime/main.py +++ /dev/null @@ -1,336 +0,0 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- -# Copyright (C) 2013-2019 British Crown (Met Office) & Contributors. -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program. If not, see . -# ----------------------------------------------------------------------------- -"""Parse and format date-time points and durations. - -SYNOPSIS - # 1. Print date time point - # 1.1 Current date time with an optional offset - isodatetime [--offset=OFFSET] - isodatetime now [--offset=OFFSET] - isodatetime ref [--offset=OFFSET] - # 1.2 Task cycle date time with an optional offset - # Assume: export ISODATETIMEREF=20371225T000000Z - isodatetime -c [--offset=OFFSET] - isodatetime -c ref [--offset=OFFSET] - # 1.3 A specific date time with an optional offset - isodatetime 20380119T031407Z [--offset=OFFSET] - - # 2. Print duration - # 2.1 Between now (+ OFFSET1) and a future date time (+ OFFSET2) - isodatetime now [--offset1=OFFSET1] 20380119T031407Z [--offset2=OFFSET2] - # 2.2 Between a date time in the past and now - isodatetime 19700101T000000Z now - # 2.3 Between task cycle time (+ OFFSET1) and a future date time - # Assume: export ISODATETIMEREF=20371225T000000Z - isodatetime -c ref [--offset1=OFFSET1] 20380119T031407Z - # 2.4 Between task cycle time and now (+ OFFSET2) - # Assume: export ISODATETIMEREF=20371225T000000Z - isodatetime -c ref now [--offset2=OFFSET2] - # 2.5 Between a date time in the past and the task cycle date time - # Assume: export ISODATETIMEREF=20371225T000000Z - isodatetime -c 19700101T000000Z ref - # 2.6 Between 2 specific date times - isodatetime 19700101T000000Z 20380119T031407Z - - # 3. Print ISO8601 duration as total amount of a unit - # 3.1 Into the total number of hours (H), minutes (M) or seconds (S) - # it represents. Note: negative durations should be escaped by telling - # the command to stop processing more options with `--` or by adding a - # backslash in front of the duration. - isodatetime --as-total=s PT1H - isodatetime --as-total=s -- -PT1H - isodatetime --as-total=s \\-PT1H - - # 4. Print a number of time points in a ISO8601 recurrence - # 4.1 Print N (default max 10) time points from start point - isodatetime R/2020/P1Y - isodatetime R5/2020/2024 - # 4.2 Print N (default max 10) time points (in reverse) from end point - isodatetime R/P1Y/2020 - -DESCRIPTION - Parse and print 1. a date time point, 2. a duration or 3. a duration - in a given unit. - - 1. With 0 or 1 argument. Print the current or the specified date time - point with an optional offset. - - 2. With 2 arguments. Print the duration between the 2 arguments. - The --as-total=UNIT option can also be used instead of the normal - print format. - - 3. With --as-total=UNIT option and a duration argument. Print the - duration in the given UNIT. - - 4. With a recurrence as argument, print N time points of the recurrence. - The --max=N (default=10) can be used to control the maximum number - of time points to print in the result. - -CALENDAR MODE - The calendar mode is determined (in order) by: - - 1. The `--calendar=MODE` option. - 2. The `ISODATETIMECALENDAR` environment variable. - 3. Default to "gregorian". - -ENVIRONMENT VARIABLES - ISODATETIMECALENDAR=gregorian|360day|365day|366day - Specify the calendar mode. - ISODATETIMEREF - Specify the current cycle time of a task in a suite. If the - `--use-task-cycle-time` option is set, the value of this environment - variable is used by the command as the reference time instead of the - current time. - -OFFSET FORMAT - `OFFSET` must follow the ISO 8601 duration representations such as - `PnW` or `PnYnMnDTnHnMnS - P` followed by a series of `nU` where `U` is - the unit (`Y`, `M`, `D`, `H`, `M`, `S`) and `n` is a positive integer, - where `T` delimits the date series from the time series if any time units - are used. `n` may also have a decimal (e.g. `PT5.5M`) part for a unit - provided no smaller units are supplied. It is not necessary to - specify zero values for units. If `OFFSET` is negative, prefix a `-`. - For example: - - * `P6D` - 6 day offset - * `PT6H` - 6 hour offset - * `PT1M` - 1 minute offset - * `-PT1M` - (negative) 1 minute offset - * `P3M` - 3 month offset - * `P2W` - 2 week offset (note no other units may be combined with weeks) - * `P2DT5.5H` - 2 day, 5.5 hour offset - * `-P2YT4S` - (negative) 2 year, 4 second offset - - The following deprecated syntax is supported: - `OFFSET` in the form `nU` where `U` is the unit (`w` for weeks, `d` for - days, `h` for hours, `m` for minutes and `s` for seconds) and `n` is a - positive or negative integer. - -PARSE FORMAT - The format for parsing a date time point should be compatible with the - POSIX strptime template format (see the strptime command help), with the - following subset supported across all date/time ranges: - - `%F`, `%H`, `%M`, `%S`, `%Y`, `%d`, `%j`, `%m`, `%s`, `%z` - - If not specified, the system will attempt to parse `DATE-TIME` using - the following formats: - - * ctime: `%a %b %d %H:%M:%S %Y` - * Unix date: `%a %b %d %H:%M:%S %Z %Y` - * Basic ISO8601: `%Y-%m-%dT%H:%M:%S`, `%Y%m%dT%H%M%S` - * Cylc: `%Y%m%d%H` - - If none of these match, the date time point will be parsed according to - the full ISO 8601 date/time standard. - -PRINT FORMAT - For printing a date time point, the print format will default to the same - format as the parse format. Also supports the isodatetime library dump - syntax for these operations which follows ISO 8601 example syntax - for - example: - - * `CCYY-MM-DDThh:mm:ss` -> `1955-11-05T09:28:00`, - * `CCYY` -> `1955`, - * `CCYY-DDD` -> `1955-309`, - * `CCYY-Www-D` -> `1955-W44-6`. - - Usage of this ISO 8601-like syntax should be as ISO 8601-compliant - as possible. - - Note that specifying an explicit timezone in this format (e.g. - `CCYY-MM-DDThh:mm:ss+0100` or `CCYYDDDThhmmZ` will automatically - adapt the date/time to that timezone i.e. apply the correct - hour/minute UTC offset. - - For printing a duration, the following can be used in format - statements: - - * `y`: years - * `m`: months - * `d`: days - * `h`: hours - * `M`: minutes - * `s`: seconds - - For example, for a duration `P57DT12H` - `y,m,d,h` -> `0,0,57,12` -""" - - -from argparse import ArgumentParser, RawDescriptionHelpFormatter -import sys - -from . import __version__ -from .datetimeoper import DateTimeOperator - - -def main(): - """Implement "isodatetime" command.""" - arg_parser = ArgumentParser( - prog='isodatetime', - formatter_class=RawDescriptionHelpFormatter, - description=__doc__) - for o_args, o_kwargs in [ - [ - ["items"], - { - "help": "Time point, duration or recurrence string", - "metavar": "ITEM", - "nargs": "*", - }, - ], - [ - ["--as-total"], - { - "action": "store", - "choices": ['H', 'M', 'S', 'h', 'm', 's'], - "dest": "duration_print_format", - "help": "Print duration as total of the specified unit.", - "metavar": "UNIT", - }, - ], - [ - ["--calendar"], - { - "action": "store", - "choices": ["360day", "365day", "366day", "gregorian"], - "help": "Set the calendar mode.", - "metavar": "MODE", - }, - ], - [ - ["--max="], - { - "action": "store", - "default": 10, - "dest": "max_results", - "help": "Specify maximum number of results.", - "metavar": "N", - "type": int, - }, - ], - [ - ["--offset1", "--offset", "-s", "-1"], - { - "action": "append", - "dest": "offsets1", - "metavar": "OFFSET", - "help": "Specify offsets for 1st date time point.", - }, - ], - [ - ["--offset2", "-2"], - { - "action": "append", - "dest": "offsets2", - "metavar": "OFFSET", - "help": "Specify offsets for 2nd date time point.", - }, - ], - [ - ["--parse-format", "-p"], - { - "metavar": "FORMAT", - "help": "Specify the format for parsing inputs.", - }, - ], - [ - ["--print-format", "--format", "-f"], - { - "metavar": "FORMAT", - "help": "Specify the format for printing results.", - }, - ], - [ - ["--ref", "-R"], - { - "action": "store", - "dest": "ref_point_str", - "help": "Specify a reference point string.", - "metavar": "REF", - }, - ], - [ - ["--utc", "-u"], - { - "action": "store_true", - "default": False, - "dest": "utc_mode", - "help": "Switch on UTC mode.", - }, - ], - [ - ["--version", "-V"], - { - "action": "store_true", - "default": False, - "dest": "version_mode", - "help": "Print version and exit.", - }, - ], - ]: - arg_parser.add_argument(*o_args, **o_kwargs) - if hasattr(arg_parser, 'parse_intermixed_args'): - args = arg_parser.parse_intermixed_args() - else: - args = arg_parser.parse_args() - if args.version_mode: - print(__version__) - return - date_time_oper = DateTimeOperator( - parse_format=args.parse_format, - utc_mode=args.utc_mode, - calendar_mode=args.calendar, - ref_point_str=args.ref_point_str) - - try: - if len(args.items) >= 2: - out = date_time_oper.diff_time_point_strs( - args.items[0], - args.items[1], - args.offsets1, - args.offsets2, - args.print_format, - args.duration_print_format) - elif args.items and args.items[0].startswith("R"): - outs = [] - for item in date_time_oper.iter_recurrence_str( - args.items[0], - args.print_format, - ): - outs.append(item) - if len(outs) >= args.max_results: - break - out = '\n'.join(outs) - elif args.items and args.duration_print_format: - out = date_time_oper.format_duration_str( - args.items[0], args.duration_print_format) - else: - time_point_str = None - if args.items: - time_point_str = args.items[0] - out = date_time_oper.process_time_point_str( - time_point_str, args.offsets1, args.print_format) - except ValueError as exc: - sys.exit(exc) - else: - print(out) - - -if __name__ == "__main__": - main() diff --git a/lib/python/isodatetime/parser_spec.py b/lib/python/isodatetime/parser_spec.py deleted file mode 100644 index b55ff97e61..0000000000 --- a/lib/python/isodatetime/parser_spec.py +++ /dev/null @@ -1,384 +0,0 @@ -# -*- coding: utf-8 -*- -# ---------------------------------------------------------------------------- -# Copyright (C) 2013-2019 British Crown (Met Office) & Contributors. -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program. If not, see . -# ---------------------------------------------------------------------------- - -"""This provides data to drive ISO 8601 parsing functionality.""" - -import re -from . import timezone - - -DATE_EXPRESSIONS = { - "basic": { - "complete": """ -CCYYMMDD -+XCCYYMMDD # '+' stands for either '+' or '-' -CCYYDDD -+XCCYYDDD -CCYYWwwD -+XCCYYWwwD""", - "reduced": """ -CCYY-MM # Deviation? Not clear if "basic" or "extended" in standard. -CCYY -CC -+XCCYY-MM # Deviation? Not clear if "basic" or "extended" in standard. -+XCCYY -+XCC -CCYYWww -+XCCYYWww""", - "truncated": """ --YYMM --YY ---MMDD ---MM ----DD -YYMMDD -YYDDD --DDD -YYWwwD -YYWww --zWwwD --zWww --WwwD --Www --W-D -""" - }, - "extended": { - "complete": """ -CCYY-MM-DD -+XCCYY-MM-DD -CCYY-DDD -+XCCYY-DDD -CCYY-Www-D -+XCCYY-Www-D""", - "reduced": """ -CCYY-MM -+XCCYY-MM -CCYY-Www -+XCCYY-Www""", - "truncated": """ --YY-MM ---MM-DD -YY-MM-DD -YY-DDD --DDD # Deviation from standard ? -YY-Www-D -YY-Www --z-WwwD --z-Www --Www-D -""" - } -} -TIME_EXPRESSIONS = { - "basic": { - "complete": """ -# No Time Zone -hhmmss - -# No Time Zone - decimals -hhmmss,tt -hhmm,nn -hh,ii -hhmmss.tt -hhmm.nn -hh.ii -""", - "reduced": """ -# No Time Zone -hhmm -hh - -# No Time Zone - decimals -""", - "truncated": """ -# No Time Zone --mmss --mm ---ss - -# No Time Zone - decimals --mmss,tt --mm,nn ---ss,tt --mmss.tt --mm.nn ---ss.tt -""" - }, - "extended": { - "complete": """ -# No Time Zone -hh:mm:ss - -# No Time Zone - decimals -hh:mm:ss,tt -hh:mm,nn -hh,ii # Deviation? Not allowed in standard ? -hh:mm:ss.tt -hh:mm.nn -hh.ii # Deviation? Not allowed in standard ? -""", - "reduced": """ -# No Time Zone -hh:mm -hh # Deviation? Not allowed in standard ? -""", - "truncated": """ -# No Time Zone --mm:ss --mm # Deviation? Not allowed in standard ? ---ss # Deviation? Not allowed in standard ? - -# No Time Zone - decimals --mm:ss,tt --mm,nn # Deviation? Not allowed in standard ? ---ss,tt # Deviation? Not allowed in standard ? --mm:ss.tt --mm.nn # Deviation? Not allowed in standard ? ---ss.tt # Deviation? Not allowed in standard ? -""" - } -} -TIME_ZONE_EXPRESSIONS = { - "basic": """ -Z -+hh -+hhmm -""", - "extended": """ -Z -+hh # Deviation? Not allowed in standard? -+hh:mm -""" -} -TIME_DESIGNATOR = "T" -_DATE_TRANSLATE_INFO = [ - (r"\+(?=X)", "(?P[-+])", - "%(year_sign)s", "year_sign"), - (r"CC", r"(?P[0-9][0-9])", - "%(century)02d", "century"), - (r"YY", r"(?P[0-9][0-9])", - "%(year_of_century)02d", "year_of_century"), - (r"MM", r"(?P[0-9][0-9])", - "%(month_of_year)02d", "month_of_year"), - (r"DDD", r"(?P[0-9][0-9][0-9])", - "%(day_of_year)03d", "day_of_year"), - (r"DD", r"(?P[0-9][0-9])", - "%(day_of_month)02d", "day_of_month"), - (r"Www", r"W(?P[0-9][0-9])", - "W%(week_of_year)02d", "week_of_year"), - (r"D", r"(?P[0-9])", - "%(day_of_week)01d", "day_of_week"), - (r"z", r"(?P[0-9])", - "%(year_of_decade)01d", "year_of_decade"), - (r"^---", r"(?P---)", - "---", None), - (r"^--", r"(?P--)", - "--", None), - (r"^-", r"(?P-)", - "-", None) -] -_TIME_TRANSLATE_INFO = [ - (r"(?<=^hh)mm", r"(?P[0-9][0-9])", - "%(minute_of_hour)02d", "minute_of_hour"), - (r"(?<=^hh:)mm", r"(?P[0-9][0-9])", - "%(minute_of_hour)02d", "minute_of_hour"), - (r"(?<=^-)mm", r"(?P[0-9][0-9])", - "%(minute_of_hour)02d", "minute_of_hour"), - (r"^hh", r"(?P[0-9][0-9])", - "%(hour_of_day)02d", "hour_of_day"), - (r",ii", r",(?P[0-9]+)", - ",%(hour_of_day_decimal_string)s", "hour_of_day_decimal_string"), - (r"\.ii", r"\.(?P[0-9]+)", - ".%(hour_of_day_decimal_string)s", "hour_of_day_decimal_string"), - (r",nn", r",(?P[0-9]+)", - ",%(minute_of_hour_decimal_string)s", "minute_of_hour_decimal_string"), - (r"\.nn", r"\.(?P[0-9]+)", - ".%(minute_of_hour_decimal_string)s", "minute_of_hour_decimal_string"), - (r"ss", r"(?P[0-9][0-9])", - "%(second_of_minute)02d", "second_of_minute"), - (r",tt", r",(?P[0-9]+)", - ",%(second_of_minute_decimal_string)s", - "second_of_minute_decimal_string"), - (r"\.tt", r"\.(?P[0-9]+)", - ".%(second_of_minute_decimal_string)s", - "second_of_minute_decimal_string"), - (r"^--", r"(?P--)", - "--", None), - (r"^-", r"(?P-)", - "-", None) -] -_TIME_ZONE_TRANSLATE_INFO = [ - (r"mm", r"(?P[0-9][0-9])", - "%(time_zone_minute_abs)02d", "time_zone_minute_abs"), - (r"mm", r"(?P[0-9][0-9])", - "%(time_zone_minute_abs)02d", "time_zone_minute_abs"), - (r"hh", r"(?P[0-9][0-9])", - "%(time_zone_hour_abs)02d", "time_zone_hour_abs"), - (r"\+", r"(?P[-+])", - "%(time_zone_sign)s", "time_zone_sign"), - (r"Z", r"(?PZ)", - "Z", None) -] - -LOCAL_TIME_ZONE_BASIC = timezone.get_local_time_zone_format() -LOCAL_TIME_ZONE_BASIC_NO_Z = LOCAL_TIME_ZONE_BASIC -if LOCAL_TIME_ZONE_BASIC_NO_Z == "Z": - LOCAL_TIME_ZONE_BASIC_NO_Z = "+0000" -LOCAL_TIME_ZONE_EXTENDED = timezone.get_local_time_zone_format( - timezone.TimeZoneFormatMode.extended) -LOCAL_TIME_ZONE_EXTENDED_NO_Z = LOCAL_TIME_ZONE_EXTENDED -if LOCAL_TIME_ZONE_EXTENDED_NO_Z == "Z": - LOCAL_TIME_ZONE_EXTENDED_NO_Z = "+0000" - -# Note: we only accept the following subset of strftime syntax. -# This is due to inconsistencies with the ISO 8601 representations. -REC_SPLIT_STRFTIME_DIRECTIVE = re.compile(r"(%\w)") -REC_STRFTIME_DIRECTIVE_TOKEN = re.compile(r"^%\w$") -STRFTIME_TRANSLATE_INFO = { - "%d": ["day_of_month"], - "%F": ["century", "year_of_century", "-", "month_of_year", "-", - "day_of_month"], - "%H": ["hour_of_day"], - "%j": ["day_of_year"], - "%m": ["month_of_year"], - "%M": ["minute_of_hour"], - "%s": ( - r"(?P[0-9]+[,.]?[0-9]*)", - "%(seconds_since_unix_epoch)s", "seconds_since_unix_epoch"), - "%S": ["second_of_minute"], - "%X": ["hour_of_day", ":", "minute_of_hour", ":", "second_of_minute"], - "%Y": ["century", "year_of_century"], - "%z": ["time_zone_sign", "time_zone_hour_abs", "time_zone_minute_abs"], -} -STRPTIME_EXCLUSIVE_GROUP_INFO = { - "%X": ("%H", "%M", "%S"), - "%F": ("%Y", "%y", "%m", "%d"), - "%s": tuple(i for i in STRFTIME_TRANSLATE_INFO if i != "%s") -} - - -class StrftimeSyntaxError(ValueError): - - """An error denoting invalid or unsupported strftime/strptime syntax.""" - - BAD_STRFTIME_INPUT = "Invalid strftime/strptime representation: {0}" - - def __str__(self): - return self.BAD_STRFTIME_INPUT.format(*self.args) - - -def get_date_translate_info(num_expanded_year_digits=2): - """Return list of 4-element tuples with date translate information. - - returns: - list: List tuples. Each tuple has 4 elements: - - regex1 (str) - regex to match a date info substitution string - - regex2 (str) - regex to capture date info - - format (str) - template string to format date info - - name (str) - name of this property - """ - expanded_year_digit_regex = r"[0-9]" * num_expanded_year_digits - return _DATE_TRANSLATE_INFO + [ - ("X", - "(?P" + expanded_year_digit_regex + ")", - "%(expanded_year_digits)0" + str(num_expanded_year_digits) + "d", - "expanded_year_digits") - ] - - -def get_time_translate_info(): - """Return list of 4-element tuples with time translate information. - - returns: - list: List tuples. Each tuple has 4 elements: - - regex1 (str) - regex to match a time info substitution string - - regex2 (str) - regex to capture a time info - - format (str) - template string to format time info - - name (str) - name of this property - """ - return _TIME_TRANSLATE_INFO - - -def get_time_zone_translate_info(): - """Return list of 4-element tuples with time zone translate information. - - returns: - list: List tuples. Each tuple has 4 elements: - - regex1 (str) - regex to match a time zone substitution string - - regex2 (str) - regex to capture a time zone - - format (str) - template string to format time zone - - name (str) - name of this property - """ - return _TIME_ZONE_TRANSLATE_INFO - - -def translate_strftime_token(strftime_token, num_expanded_year_digits=2): - """Convert a strftime format into our own dump format.""" - return _translate_strftime_token( - strftime_token, dump_mode=True, - num_expanded_year_digits=num_expanded_year_digits - ) - - -def translate_strptime_token(strptime_token, num_expanded_year_digits=2): - """Convert a strptime format into our own parsing format.""" - return _translate_strftime_token( - strptime_token, dump_mode=False, - num_expanded_year_digits=num_expanded_year_digits - ) - - -def _translate_strftime_token(strftime_token, dump_mode=False, - num_expanded_year_digits=2): - if strftime_token not in STRFTIME_TRANSLATE_INFO: - raise StrftimeSyntaxError(strftime_token) - our_translation = "" - our_translate_info = ( - get_date_translate_info( - num_expanded_year_digits=num_expanded_year_digits) + - get_time_translate_info() + - get_time_zone_translate_info() - ) - attr_names = STRFTIME_TRANSLATE_INFO[strftime_token] - if isinstance(attr_names, str): - if dump_mode: - return attr_names, [] - return re.escape(attr_names), [] - if isinstance(attr_names, tuple): - (substitute, format_, name) = attr_names - if dump_mode: - our_translation += format_ - else: - our_translation += substitute - return our_translation, [name] - attr_names = list(attr_names) - for attr_name in list(attr_names): - for _, substitute, format_, name in our_translate_info: - if name == attr_name: - if dump_mode: - our_translation += format_ - else: - our_translation += substitute - break - else: - # Not an attribute name, just a delimiter or something. - our_translation += attr_name - attr_names.remove(attr_name) - return our_translation, attr_names diff --git a/lib/python/isodatetime/parsers.py b/lib/python/isodatetime/parsers.py deleted file mode 100644 index 332a51581a..0000000000 --- a/lib/python/isodatetime/parsers.py +++ /dev/null @@ -1,603 +0,0 @@ -# -*- coding: utf-8 -*- -# ---------------------------------------------------------------------------- -# Copyright (C) 2013-2019 British Crown (Met Office) & Contributors. -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program. If not, see . -# ---------------------------------------------------------------------------- - -"""This provides ISO 8601 parsing functionality.""" - -import re -import sre_constants - -from . import data -from . import parser_spec -from . import timezone - - -class ISO8601SyntaxError(ValueError): - - """An error denoting invalid input syntax.""" - - BAD_TIME_INPUT = "Invalid ISO 8601 {0} representation: {1}" - - def __str__(self): - return self.BAD_TIME_INPUT.format(*self.args) - - -class StrptimeConversionError(ValueError): - - """An error denoting bad conversion from a strftime/strptime format.""" - - BAD_CONVERSION = "Bad conversion for strftime/strptime input '{0}': '{1}'" - - def __str__(self): - return self.BAD_CONVERSION.format(*self.args) - - -class TimeRecurrenceParser(object): - - """Parser for ISO 8601 recurrence expressions. - - Keyword arguments: - timepoint_parser (default None) should be an instance of - TimePointParser, or None to use a normal TimePointParser instance. - duration_parser (default None) should be an instance of - DurationParser, or None to generate a normal - DurationParser. - - Callable (via self.parse method) with an ISO 8601-compliant - recurrence pattern - this returns a TimeRecurrence instance. - - """ - - RECURRENCE_REGEXES = [ - re.compile(r"^R(?P\d+)/(?P[^P][^/]*)/(?P[^P].*)$"), - re.compile(r"^R(?P\d+)?/(?P[^P][^/]*)/(?PP.+)$"), - re.compile(r"^R(?P\d+)?/(?PP.+)/(?P[^P].*)$")] - - def __init__(self, timepoint_parser=None, duration_parser=None): - if timepoint_parser is None: - self.timepoint_parser = TimePointParser() - else: - self.timepoint_parser = timepoint_parser - if duration_parser is None: - self.duration_parser = DurationParser() - else: - self.duration_parser = duration_parser - - def parse(self, expression): - """Parse a recurrence string into a TimeRecurrence instance.""" - for regex in self.RECURRENCE_REGEXES: - result = regex.search(expression) - if not result: - continue - result_map = result.groupdict() - repetitions = None - start_point = None - end_point = None - duration = None - if "reps" in result_map and result_map["reps"] is not None: - repetitions = int(result_map["reps"]) - if "start" in result_map: - start_point = self.timepoint_parser.parse(result_map["start"]) - if "end" in result_map: - end_point = self.timepoint_parser.parse(result_map["end"]) - if "intv" in result_map: - duration = self.duration_parser.parse( - result_map["intv"]) - return data.TimeRecurrence( - repetitions=repetitions, - start_point=start_point, - end_point=end_point, - duration=duration - ) - raise ISO8601SyntaxError("recurrence", expression) - - __call__ = parse - - -class TimePointParser(object): - - """Container for ISO 8601 date/time expressions. - - Keyword arguments: - num_expanded_year_digits (default 2) specifies the extra year - digits allowed by the ISO standard - for example, 1995 can be - written as +001995 with 2 extra year digits. - - allow_truncated (default False) specifies that ISO 8601:2000 - truncations are allowed (not allowed in the ISO 8601:2004 - standard which supersedes it). - - allow_only_basic (default False) specifies that only the basic - forms of date and time in the ISO standard are allowed (no - extraneous punctuation). This means that "2000-01-02T01:14:02" - is not allowed, and must be written as "20000102T011402". - - assumed_time_zone (default None) is a tuple of hours (integer) - and minutes (integer) that specifies that dates and times - without time zone information should be set to have a time zone - whose offset from UTC is the (hours, minutes) information in this - variable. To assume UTC, set this to (0, 0). - - default_to_unknown_time_zone (default False) specifies that - dates and times without time zone information (in the absence of - assumed_time_zone) should be left with an unknown time zone - setting. Otherwise, the current local time zone will be used. - - dump_format (default None) specifies a default custom dump format - string for TimePoint instances. See data.TimePoint documentation - for syntax. - - """ - - def __init__(self, num_expanded_year_digits=2, - allow_truncated=False, - allow_only_basic=False, - assumed_time_zone=None, - default_to_unknown_time_zone=False, - dump_format=None): - self.expanded_year_digits = num_expanded_year_digits - self.allow_truncated = allow_truncated - self.allow_only_basic = allow_only_basic - self.assumed_time_zone = assumed_time_zone - self.default_to_unknown_time_zone = default_to_unknown_time_zone - self.dump_format = dump_format - self._generate_regexes() - - def _generate_regexes(self): - """Generate combined date time strings.""" - date_map = parser_spec.DATE_EXPRESSIONS - time_map = parser_spec.TIME_EXPRESSIONS - time_zone_map = parser_spec.TIME_ZONE_EXPRESSIONS - self._date_regex_map = {} - self._time_regex_map = {} - self._time_zone_regex_map = {} - format_ok_keys = ["basic", "extended"] - if self.allow_only_basic: - format_ok_keys = ["basic"] - for format_type in format_ok_keys: - self._date_regex_map.setdefault(format_type, {}) - self._time_regex_map.setdefault(format_type, {}) - self._time_zone_regex_map.setdefault(format_type, []) - for date_key in date_map[format_type].keys(): - self._date_regex_map[format_type].setdefault(date_key, []) - regex_list = self._date_regex_map[format_type][date_key] - for date_expr in self.get_expressions( - date_map[format_type][date_key]): - date_regex = self.parse_date_expression_to_regex( - date_expr) - regex_list.append([re.compile(date_regex), date_expr]) - for time_key in time_map[format_type].keys(): - self._time_regex_map[format_type].setdefault(time_key, []) - regex_list = self._time_regex_map[format_type][time_key] - for time_expr in self.get_expressions( - time_map[format_type][time_key]): - time_regex = self.parse_time_expression_to_regex( - time_expr) - regex_list.append([re.compile(time_regex), time_expr]) - for time_zone_expr in self.get_expressions( - time_zone_map[format_type]): - time_zone_regex = self.parse_time_zone_expression_to_regex( - time_zone_expr) - self._time_zone_regex_map[format_type].append( - [re.compile(time_zone_regex), time_zone_expr]) - - @staticmethod - def get_expressions(text): - """Yield valid expressions from text.""" - for line in text.splitlines(): - line_text = line.strip() - if not line_text or line_text.startswith("#"): - continue - expr_text = line_text.split("#", 1)[0].strip() - yield expr_text - - def parse_date_expression_to_regex(self, expression): - """Construct regular expressions for the date.""" - for expr_regex, substitute, _, _ in ( - parser_spec.get_date_translate_info( - self.expanded_year_digits)): - expression = re.sub(expr_regex, substitute, expression) - expression = "^" + expression + "$" - return expression - - @staticmethod - def parse_time_expression_to_regex(expression): - """Construct regular expressions for the time.""" - for expr_regex, substitute, _, _ in ( - parser_spec.get_time_translate_info()): - expression = re.sub(expr_regex, substitute, expression) - expression = "^" + expression + "$" - return expression - - @staticmethod - def parse_time_zone_expression_to_regex(expression): - """Construct regular expressions for the time zone.""" - for expr_regex, substitute, _, _ in ( - parser_spec.get_time_zone_translate_info()): - expression = re.sub(expr_regex, substitute, expression) - expression = "^" + expression + "$" - return expression - - def parse(self, timepoint_string, dump_format=None, dump_as_parsed=False): - """Parse a user-supplied timepoint string.""" - date_info, time_info, parsed_expr = self.get_info(timepoint_string) - if dump_as_parsed: - dump_format = parsed_expr - return self._create_timepoint_from_info( - date_info, time_info, dump_format=dump_format, - truncated_dump_format=dump_format) - - def _create_timepoint_from_info(self, date_info, time_info, - dump_format=None, - truncated_dump_format=None): - info = {} - truncated_property = None - if date_info.get("truncated"): - if "year_of_decade" in date_info: - truncated_property = "year_of_decade" - if "year_of_century" in date_info: - truncated_property = "year_of_century" - elif ("century" not in date_info and - "year_of_century" in date_info): - truncated_property = "year_of_century" - date_info["truncated"] = True - is_year_present = True - if date_info.get("truncated"): - is_year_present = False - for property_ in ["year", "year_of_decade", "century", - "year_of_century", "expanded_year", - "year_sign"]: - if date_info.get(property_) is not None: - is_year_present = True - if is_year_present: - year = int(date_info.get("year", 0)) - if "year_of_decade" in date_info: - year += int(date_info.pop("year_of_decade")) - truncated_property = "year_of_decade" - year += int(date_info.pop("year_of_century", 0)) - year += 100 * int(date_info.pop("century", 0)) - expanded_year = date_info.pop("expanded_year", 0) - if expanded_year: - date_info["expanded_year_digits"] = self.expanded_year_digits - year += 10000 * int(expanded_year) - if date_info.pop("year_sign", "+") == "-": - year *= -1 - date_info["year"] = year - for key, value in date_info.items(): - try: - date_info[key] = int(value) - except (TypeError, ValueError): - pass - info.update(date_info) - for key, value in list(time_info.items()): - if key.endswith("_decimal"): - value = "0." + value - try: - value = float(value) - except (IOError, TypeError, ValueError): - pass - if key == "time_zone_utc" and value == "Z": - time_info.pop(key) - time_info.update({"time_zone_hour": 0, - "time_zone_minute": 0}) - continue - time_info[key] = value - info.update(time_info) - if info.pop("truncated", False): - info["truncated"] = True - if truncated_property is not None: - info["truncated_property"] = truncated_property - if dump_format is None and self.dump_format: - dump_format = self.dump_format - if dump_format is not None: - info.update({"dump_format": dump_format}) - if truncated_dump_format is not None: - info.update({"truncated_dump_format": truncated_dump_format}) - return data.TimePoint(**info) - - def strptime(self, strptime_data_string, strptime_format_string, - dump_format=None): - """Implement equivalent of Python 2's datetime.datetime.strptime. - - Return an isodatetime.data.TimePoint representing - strptime_data_string based on the format given in - strptime_format_string. - dump_format is a custom dump format string (not in strftime - format). - - """ - split_format = parser_spec.REC_SPLIT_STRFTIME_DIRECTIVE.split( - strptime_format_string) - regex = "^" - for item in split_format: - if parser_spec.REC_STRFTIME_DIRECTIVE_TOKEN.search(item): - regex += parser_spec.translate_strptime_token(item)[0] - else: - regex += re.escape(item) - regex += "$" - return self._parse_from_custom_regex( - regex, strptime_data_string, - dump_format=dump_format, source=strptime_format_string) - - def _parse_from_custom_regex(self, regex, data_string, dump_format=None, - source=None): - """Parse data_string according to the regular expression in regex.""" - try: - compiled_regex = re.compile(regex) - except sre_constants.error: - raise StrptimeConversionError(source, regex) - result = compiled_regex.match(data_string) - if not result: - raise StrptimeConversionError(source, data_string) - info = result.groupdict() - for property_, value in list(info.items()): - if property_ in data.PARSE_PROPERTY_TRANSLATORS: - info.pop(property_) - translator = data.PARSE_PROPERTY_TRANSLATORS[property_] - info.update(translator(value)) - date_info_keys = [] - for item in parser_spec.get_date_translate_info( - self.expanded_year_digits): - date_info_keys.append(item[3]) - time_info_keys = [] - for item in parser_spec.get_time_translate_info(): - time_info_keys.append(item[3]) - date_info = {} - time_info = {} - time_zone_info = {} - for key, value in info.items(): - if key in date_info_keys: - date_info[key] = value - elif key in time_info_keys: - time_info[key] = value - else: - time_zone_info[key] = value - time_zone_info = self.process_time_zone_info(time_zone_info) - time_info.update(time_zone_info) - return self._create_timepoint_from_info( - date_info, time_info, dump_format=dump_format) - - def get_date_info(self, date_string, bad_types=None): - """Return the format and properties from a date string.""" - type_keys = ["complete", "truncated", "reduced"] - if bad_types is not None: - for type_key in bad_types: - type_keys.remove(type_key) - if not self.allow_truncated and "truncated" in type_keys: - type_keys.remove("truncated") - for format_key, type_regex_map in self._date_regex_map.items(): - for type_key in type_keys: - regex_list = type_regex_map[type_key] - for regex, expr in regex_list: - result = regex.match(date_string) - if result: - return ((format_key, type_key, expr), - result.groupdict()) - raise ISO8601SyntaxError("date", date_string) - - def get_time_info(self, time_string, bad_formats=None, bad_types=None): - """Return the properties from a time string.""" - if bad_formats is None: - bad_formats = [] - if bad_types is None: - bad_types = [] - for format_key, type_regex_map in self._time_regex_map.items(): - if format_key in bad_formats: - continue - for type_key, regex_list in type_regex_map.items(): - if type_key in bad_types: - continue - for regex, expr in regex_list: - result = regex.match(time_string) - if result: - return expr, result.groupdict() - raise ISO8601SyntaxError("time", time_string) - - def get_time_zone_info(self, time_zone_string, bad_formats=None): - """Return the properties from a time zone string.""" - if bad_formats is None: - bad_formats = [] - for format_key, regex_list in self._time_zone_regex_map.items(): - if format_key in bad_formats: - continue - for regex, expr in regex_list: - result = regex.match(time_zone_string) - if result: - return expr, result.groupdict() - raise ISO8601SyntaxError("time zone", time_zone_string) - - def get_info(self, timepoint_string): - """Return the date and time properties from a timepoint string.""" - date_time_time_zone = timepoint_string.split( - parser_spec.TIME_DESIGNATOR) - parsed_expr = "" - if len(date_time_time_zone) == 1: - date = date_time_time_zone[0] - keys, date_info = self.get_date_info(date) - format_key, type_key, date_expr = keys - parsed_expr += date_expr - time_info = {} - time_zone_info = ( - self.process_time_zone_info({})) - time_info.update(time_zone_info) - else: - date, time_time_zone = date_time_time_zone - if not date and self.allow_truncated: - keys = (None, "truncated", "") - date_info = {"truncated": True} - else: - keys, date_info = self.get_date_info(date, - bad_types=["reduced"]) - format_key, type_key, date_expr = keys - parsed_expr += date_expr - bad_formats = [] - if format_key == "basic": - bad_formats = ["extended"] - if format_key == "extended": - bad_formats = ["basic"] - if type_key == "truncated": - # Do not force basic/extended formatting for truncated dates. - bad_formats = [] - bad_types = ["truncated"] - if date_info.get("truncated"): - bad_types = [] - if time_time_zone.endswith("Z"): - time, time_zone = time_time_zone[:-1], "Z" - elif "+" in time_time_zone: - time, time_zone = time_time_zone.split("+") - time_zone = "+" + time_zone - elif "-" in time_time_zone: - time, time_zone = time_time_zone.rsplit("-", 1) - time_zone = "-" + time_zone - # Make sure this isn't just a truncated time. - try: - time_expr, time_info = self.get_time_info( - time, - bad_formats=bad_formats, - bad_types=bad_types - ) - time_zone_expr, time_zone_info = self.get_time_zone_info( - time_zone, - bad_formats=bad_formats - ) - except ISO8601SyntaxError: - time = time_time_zone - time_zone = None - else: - time = time_time_zone - time_zone = None - if time_zone is None: - time_zone_info = {} - time_zone_expr = "" - time_zone_info = ( - self.process_time_zone_info(time_zone_info)) - else: - time_zone_expr, time_zone_info = self.get_time_zone_info( - time_zone, - bad_formats=bad_formats - ) - time_zone_info = self.process_time_zone_info(time_zone_info) - time_expr, time_info = self.get_time_info( - time, bad_formats=bad_formats, bad_types=bad_types) - parsed_expr += parser_spec.TIME_DESIGNATOR + ( - time_expr + time_zone_expr) - time_info.update(time_zone_info) - return date_info, time_info, parsed_expr - - def process_time_zone_info(self, time_zone_info=None): - """Rationalise time zone data and set defaults if appropriate.""" - if time_zone_info is None: - time_zone_info = {} - if not time_zone_info: - # There is no time zone information specified. - if self.assumed_time_zone is None: - # No given value to assume. - if self.default_to_unknown_time_zone: - # Return no time zone information. - return {} - # Set the time zone to the current local time zone. - utc_hour_offset, utc_minute_offset = ( - timezone.get_local_time_zone()) - time_zone_info["time_zone_hour"] = utc_hour_offset - time_zone_info["time_zone_minute"] = utc_minute_offset - return time_zone_info - else: - # Set the time zone to a given value. - utc_hour_offset, utc_minute_offset = self.assumed_time_zone - time_zone_info["time_zone_hour"] = utc_hour_offset - time_zone_info["time_zone_minute"] = utc_minute_offset - return time_zone_info - if time_zone_info.pop("time_zone_sign", "+") == "-": - time_zone_info["time_zone_hour"] = ( - -int(time_zone_info["time_zone_hour"])) - if "time_zone_minute" in time_zone_info: - time_zone_info["time_zone_minute"] = ( - -int(time_zone_info["time_zone_minute"])) - return time_zone_info - - -class DurationParser(object): - - """Parser for ISO 8601 Durations (durations).""" - - DURATION_REGEXES = [ - re.compile(r"""^P(?:(?P\d+)Y)? - (?:(?P\d+)M)? - (?:(?P\d+)D)?$""", re.X), - re.compile(r"""^P(?:(?P\d+)Y)? - (?:(?P\d+)M)? - (?:(?P\d+)D)? - T(?:(?P\d.*)H)? - (?:(?P\d.*)M)? - (?:(?P\d.*)S)?$""", re.X), - re.compile(r"""^P(?P\d+)W$""", re.X) - ] - - def parse(self, expression): - """Parse an ISO duration expression into a Duration instance.""" - sign_factor = 1 - if expression.startswith("-"): - sign_factor = -1 - expression = expression[1:] - for rec_regex in self.DURATION_REGEXES: - result = rec_regex.search(expression) - if not result: - continue - result_map = result.groupdict() - for key, value in list(result_map.items()): - if value is None: - result_map.pop(key) - continue - if key in ["years", "months", "days", "weeks"]: - value = int(value) - else: - if "," in value: - value = value.replace(",", ".") - value = float(value) - result_map[key] = value * sign_factor - return data.Duration(**result_map) - if expression.startswith("P") and sign_factor != -1: - # TimePoint-like duration - don't allow our negative extension. - try: - timepoint = parse_timepoint_expression( - expression[1:], allow_truncated=False, - assumed_time_zone=(0, 0) - ) - except ISO8601SyntaxError: - raise ISO8601SyntaxError("duration", expression) - if timepoint.get_is_week_date(): - raise ISO8601SyntaxError("duration", expression) - result_map = {} - result_map["years"] = timepoint.year - if timepoint.get_is_calendar_date(): - result_map["months"] = timepoint.month_of_year - result_map["days"] = timepoint.day_of_month - if timepoint.get_is_ordinal_date(): - result_map["days"] = timepoint.day_of_year - result_map["hours"] = timepoint.hour_of_day - if timepoint.minute_of_hour is not None: - result_map["minutes"] = timepoint.minute_of_hour - if timepoint.second_of_minute is not None: - result_map["seconds"] = timepoint.second_of_minute - return data.Duration(**result_map) - raise ISO8601SyntaxError("duration", expression) - - -def parse_timepoint_expression(timepoint_expression, **kwargs): - """Return a data model that represents timepoint_expression.""" - parser = TimePointParser(**kwargs) - return parser.parse(timepoint_expression) diff --git a/lib/python/isodatetime/run_tests b/lib/python/isodatetime/run_tests deleted file mode 100755 index 5c784a73e4..0000000000 --- a/lib/python/isodatetime/run_tests +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/bash -# -*- coding: utf-8 -*- -#----------------------------------------------------------------------------- -# Copyright (C) 2013-2018 British Crown (Met Office) & Contributors. -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program. If not, see . -#----------------------------------------------------------------------------- -# Run tests for the ISO 8601 parsing and data model functionality.""" -#----------------------------------------------------------------------------- -set -eu -cd "$(dirname "$0")/../" -TZ=UTC python3 -m isodatetime.tests "$@" diff --git a/lib/python/isodatetime/tests/__init__.py b/lib/python/isodatetime/tests/__init__.py deleted file mode 100644 index 30733c0986..0000000000 --- a/lib/python/isodatetime/tests/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -# -*- coding: utf-8 -*- -# ---------------------------------------------------------------------------- -# Copyright (C) 2013-2019 British Crown (Met Office) & Contributors. -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program. If not, see . -# ---------------------------------------------------------------------------- -"""Test modules for "isodatetime".""" diff --git a/lib/python/isodatetime/tests/test_00.py b/lib/python/isodatetime/tests/test_00.py deleted file mode 100644 index aea5ce50e0..0000000000 --- a/lib/python/isodatetime/tests/test_00.py +++ /dev/null @@ -1,1766 +0,0 @@ -# -*- coding: utf-8 -*- -# ---------------------------------------------------------------------------- -# Copyright (C) 2013-2019 British Crown (Met Office) & Contributors. -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program. If not, see . -# ---------------------------------------------------------------------------- - -"""This tests the ISO 8601 parsing and data model functionality.""" - -import copy -import datetime -from itertools import chain -import pytest -import unittest -from unittest.mock import patch, MagicMock, Mock - -from isodatetime import data -from isodatetime import dumpers -from isodatetime import parsers -from isodatetime import parser_spec -from isodatetime import timezone - - -def get_timeduration_tests(): - """Yield tests for the duration class.""" - tests = { - "get_days_and_seconds": [ - ([], {"hours": 25}, (1, 3600)), - ([], {"seconds": 59}, (0, 59)), - ([], {"minutes": 10}, (0, 600)), - ([], {"days": 5, "minutes": 2}, (5, 120)), - ([], {"hours": 2, "minutes": 5, "seconds": 11.5}, (0, 7511.5)), - ([], {"hours": 23, "minutes": 1446}, (1, 83160)) - ], - "get_seconds": [ - ([], {"hours": 25}, 90000), - ([], {"seconds": 59}, 59), - ([], {"minutes": 10}, 600), - ([], {"days": 5, "minutes": 2}, 432120), - ([], {"hours": 2, "minutes": 5, "seconds": 11.5}, 7511.5), - ([], {"hours": 23, "minutes": 1446}, 169560) - ] - } - for method, method_tests in tests.items(): - for method_args, test_props, ctrl_results in method_tests: - yield test_props, method, method_args, ctrl_results - - -def get_timedurationparser_tests(): - """Yield tests for the duration parser.""" - test_expressions = { - "P3Y": {"years": 3}, - "P90Y": {"years": 90}, - "P1Y2M": {"years": 1, "months": 2}, - "P20Y2M": {"years": 20, "months": 2}, - "P2M": {"months": 2}, - "P52M": {"months": 52}, - "P20Y10M2D": {"years": 20, "months": 10, "days": 2}, - "P1Y3D": {"years": 1, "days": 3}, - "P4M1D": {"months": 4, "days": 1}, - "P3Y404D": {"years": 3, "days": 404}, - "P30Y2D": {"years": 30, "days": 2}, - "PT6H": {"hours": 6}, - "PT1034H": {"hours": 1034}, - "P3YT4H2M": {"years": 3, "hours": 4, "minutes": 2}, - "P30Y2DT10S": {"years": 30, "days": 2, "seconds": 10}, - "PT2S": {"seconds": 2}, - "PT2.5S": {"seconds": 2.5}, - "PT2,5S": {"seconds": 2.5}, - "PT5.5023H": {"hours": 5.5023}, - "PT5,5023H": {"hours": 5.5023}, - "P5W": {"weeks": 5}, - "P100W": {"weeks": 100}, - "P0004-03-02T01": {"years": 4, "months": 3, "days": 2, - "hours": 1}, - "P0004-03-00": {"years": 4, "months": 3}, - "P0004-078": {"years": 4, "days": 78}, - "P0004-078T10,5": {"years": 4, "days": 78, "hours": 10.5}, - "P00000020T133702": {"days": 20, "hours": 13, "minutes": 37, - "seconds": 2}, - "-P3YT4H2M": {"years": -3, "hours": -4, "minutes": -2}, - "-PT5M": {"minutes": -5}, - "-P7Y": {"years": -7, "hours": 0} - } - for expression, ctrl_result in test_expressions.items(): - ctrl_data = str(data.Duration(**ctrl_result)) - yield expression, ctrl_data - - -def get_timedurationdumper_tests(): - """Yield tests for the duration dumper.""" - test_expressions = { - "P3Y": {"years": 3}, - "P90Y": {"years": 90}, - "P1Y2M": {"years": 1, "months": 2}, - "P20Y2M": {"years": 20, "months": 2}, - "P2M": {"months": 2}, - "P52M": {"months": 52}, - "P20Y10M2D": {"years": 20, "months": 10, "days": 2}, - "P1Y3D": {"years": 1, "days": 3}, - "P4M1D": {"months": 4, "days": 1}, - "P3Y404D": {"years": 3, "days": 404}, - "P30Y2D": {"years": 30, "days": 2}, - "PT6H": {"hours": 6}, - "PT1034H": {"hours": 1034}, - "P3YT4H2M": {"years": 3, "hours": 4, "minutes": 2}, - "P30Y2DT10S": {"years": 30, "days": 2, "seconds": 10}, - "PT2S": {"seconds": 2}, - "PT2,5S": {"seconds": 2.5}, - "PT5,5023H": {"hours": 5.5023}, - "P5W": {"weeks": 5}, - "P100W": {"weeks": 100}, - "-P3YT4H2M": {"years": -3, "hours": -4, "minutes": -2}, - "-PT5M": {"minutes": -5}, - "-P7Y": {"years": -7, "hours": 0}, - "PT1H": {"seconds": 3600, "standardize": True}, - "P1DT5M": {"minutes": 1445, "standardize": True}, - "PT59S": {"seconds": 59, "standardize": True}, - "PT1H4M56S": {"minutes": 10, "seconds": 3296, "standardize": True}, - } - for expression, ctrl_result in test_expressions.items(): - yield expression, ctrl_result - - -def get_timepoint_dumper_tests(): - """Yield tests for custom timepoint dumps.""" - return [ - ( - {"year": 44, "month_of_year": 1, "day_of_month": 4, - "hour_of_day": 5, "minute_of_hour": 1, "second_of_minute": 2, - "time_zone_hour": 0, "time_zone_minute": 0}, - [("CCYY-MMDDThhmmZ", "0044-0104T0501Z"), - ("YYDDDThh:mm:ss", "44004T05:01:02"), - ("WwwD", "W011"), - ("CCDDDThh*ss-0600", "00003T23*02-0600"), - ("+XCCYY-MM-DDThh:mm:ss-11:45", - "+000044-01-03T17:16:02-11:45"), - ("+XCCYYMM-DDThh-01:00", "+00004401-04T04-01:00"), - ("+XCCYYMM-DDThh+13:00", "+00004401-04T18+13:00"), - ("+XCCYYMM-DDThh-0100", "+00004401-04T04-0100"), - ("+XCCYYMM-DDThh+1300", "+00004401-04T18+1300"), - ("+XCCYYMMDDThh-0100", "+0000440104T04-0100"), - ("+XCCYYMMDDThh+13", "+0000440104T18+13"), - ("+XCCYYMMDDThh+hhmm", "+0000440104T05+0000"), - ("+XCCYY-MM-DDThh:mm:ss+hh:mm", - "+000044-01-04T05:01:02+00:00"), - ("DD/MM/CCYY is a silly format", "04/01/0044 is a silly format"), - ("ThhZ", "T05Z"), - ("%Y-%m-%dT%H:%M", "0044-01-04T05:01")] - ), - ( - {"year": 500200, "month_of_year": 7, "day_of_month": 28, - "expanded_year_digits": 2, "hour_of_day": 0, - "hour_of_day_decimal": 0.4356, "time_zone_hour": -8, - "time_zone_minute": -30}, - [("+XCCYY-MMDDThhmmZ", "+500200-0728T0856Z"), - ("+XCCYYDDDThh:mm:ss", "+500200209T00:26:08"), - ("WwwD", "W311"), - ("+XCCDDDThh*ss-0600", "+5002209T02*08-0600"), - ("+XCCYY-MM-DDThh:mm:ss-11:45", - "+500200-07-27T21:11:08-11:45"), - ("+XCCYYMM-DDThhmm-01:00", "+50020007-28T0756-01:00"), - ("+XCCYYMM-DDThhmm+13:00", "+50020007-28T2156+13:00"), - ("+XCCYYMM-DDThhmm-0100", "+50020007-28T0756-0100"), - ("+XCCYYMM-DDThhmm+1300", "+50020007-28T2156+1300"), - ("+XCCYYMMDDThhmm-0100", "+5002000728T0756-0100"), - ("+XCCYYMMDDThhmm+13", "+5002000728T2156+13"), - ("+XCCYYMMDDThh+hhmm", "+5002000728T00-0830"), - ("+XCCYYWwwDThhmm+hh", "+500200W311T0026-08"), - ("+XCCYYDDDThhmm+hh", "+500200209T0026-08"), - ("+XCCYY-MM-DDThh:mm:ss+hh:mm", - "+500200-07-28T00:26:08-08:30"), - ("+XCCYY-MM-DDThh:mm:ssZ", "+500200-07-28T08:56:08Z"), - ("DD/MM/+XCCYY is a silly format", - "28/07/+500200 is a silly format"), - ("ThhmmZ", "T0856Z"), - ("%m-%dT%H:%M", "07-28T00:26")] - ), - ( - {"year": -56, "day_of_year": 318, "expanded_year_digits": 2, - "hour_of_day": 5, "minute_of_hour": 1, "time_zone_hour": 6}, - [("+XCCYY-MMDDThhmmZ", "-000056-1112T2301Z"), - ("+XCCYYDDDThh:mm:ss", "-000056318T05:01:00"), - ("WwwD", "W461"), - ("+XCCDDDThh*ss-0600", "-0000317T17*00-0600"), - ("+XCCYY-MM-DDThh:mm:ss-11:45", - "-000056-11-12T11:16:00-11:45"), - ("+XCCYYMM-DDThhmm-01:00", "-00005611-12T2201-01:00"), - ("+XCCYYMM-DDThhmm+13:00", "-00005611-13T1201+13:00"), - ("+XCCYYMM-DDThhmm-0100", "-00005611-12T2201-0100"), - ("+XCCYYMM-DDThhmm+1300", "-00005611-13T1201+1300"), - ("+XCCYYMMDDThhmm-0100", "-0000561112T2201-0100"), - ("+XCCYYMMDDThhmm+13", "-0000561113T1201+13"), - ("+XCCYYMMDDThh+hhmm", "-0000561113T05+0600"), - ("+XCCYYWwwDThhmm+hh", "-000056W461T0501+06"), - ("+XCCYYDDDThhmm+hh", "-000056318T0501+06"), - ("+XCCYY-MM-DDThh:mm:ss+hh:mm", - "-000056-11-13T05:01:00+06:00"), - ("+XCCYY-MM-DDThh:mm:ssZ", "-000056-11-12T23:01:00Z"), - ("DD/MM/+XCCYY is a silly format", - "13/11/-000056 is a silly format"), - ("ThhmmZ", "T2301Z"), - ("%m-%dT%H:%M", "11-13T05:01")] - ), - ( - {"year": 1000, "week_of_year": 1, "day_of_week": 1, - "time_zone_hour": 0}, - [("CCYY-MMDDThhmmZ", "0999-1230T0000Z"), - ("CCYY-DDDThhmmZ", "0999-364T0000Z"), - ("CCYY-Www-DThhmm+0200", "1000-W01-1T0200+0200"), - ("CCYY-Www-DThhmm-0200", "0999-W52-7T2200-0200"), - ("%Y-%m-%dT%H:%M", "0999-12-30T00:00")] - ), - ( - {"year": 999, "day_of_year": 364, "time_zone_hour": 0}, - [("CCYY-MMDDThhmmZ", "0999-1230T0000Z"), - ("CCYY-DDDThhmmZ", "0999-364T0000Z"), - ("CCYY-Www-DThhmm+0200", "1000-W01-1T0200+0200"), - ("CCYY-Www-DThhmm-0200", "0999-W52-7T2200-0200"), - ("%Y-%m-%dT%H:%M", "0999-12-30T00:00")] - ) - ] - - -def get_timepointdumper_failure_tests(): - """Yield tests that raise exceptions for custom time point dumps.""" - bounds_error = dumpers.TimePointDumperBoundsError - return [ - ( - {"year": 10000, "month_of_year": 1, "day_of_month": 4, - "time_zone_hour": 0, "time_zone_minute": 0}, - [("CCYY-MMDDThhmmZ", bounds_error, 0), - ("%Y-%m-%dT%H:%M", bounds_error, 0)] - ), - ( - {"year": -10000, "month_of_year": 1, "day_of_month": 4, - "time_zone_hour": 0, "time_zone_minute": 0}, - [("CCYY-MMDDThhmmZ", bounds_error, 0), - ("%Y-%m-%dT%H:%M", bounds_error, 0)] - ), - ( - {"year": 10000, "month_of_year": 1, "day_of_month": 4, - "time_zone_hour": 0, "time_zone_minute": 0}, - [("CCYY-MMDDThhmmZ", bounds_error, 2)] - ), - ( - {"year": -10000, "month_of_year": 1, "day_of_month": 4, - "time_zone_hour": 0, "time_zone_minute": 0}, - [("CCYY-MMDDThhmmZ", bounds_error, 2)] - ), - ( - {"year": 1000000, "month_of_year": 1, "day_of_month": 4, - "time_zone_hour": 0, "time_zone_minute": 0}, - [("+XCCYY-MMDDThhmmZ", bounds_error, 2)] - ), - ( - {"year": -1000000, "month_of_year": 1, "day_of_month": 4, - "time_zone_hour": 0, "time_zone_minute": 0}, - [("+XCCYY-MMDDThhmmZ", bounds_error, 2)] - ) - ] - - -def get_timepointparser_tests(allow_only_basic=False, - allow_truncated=False, - skip_time_zones=False): - """Yield tests for the time point parser.""" - # Note: test dates assume 2 expanded year digits. - test_date_map = { - "basic": { - "complete": { - "00440104": {"year": 44, "month_of_year": 1, - "day_of_month": 4}, - "+5002000830": {"year": 500200, "month_of_year": 8, - "day_of_month": 30, - "expanded_year_digits": 2}, - "-0000561113": {"year": -56, "month_of_year": 11, - "day_of_month": 13, - "expanded_year_digits": 2}, - "-1000240210": {"year": -100024, "month_of_year": 2, - "day_of_month": 10, - "expanded_year_digits": 2}, - "1967056": {"year": 1967, "day_of_year": 56}, - "+123456078": {"year": 123456, "day_of_year": 78, - "expanded_year_digits": 2}, - "-004560134": {"year": -4560, "day_of_year": 134, - "expanded_year_digits": 2}, - "1001W011": {"year": 1001, "week_of_year": 1, - "day_of_week": 1}, - "+000001W457": {"year": 1, "week_of_year": 45, - "day_of_week": 7, - "expanded_year_digits": 2}, - "-010001W053": {"year": -10001, "week_of_year": 5, - "day_of_week": 3, "expanded_year_digits": 2} - }, - "reduced": { - "4401-03": {"year": 4401, "month_of_year": 3}, - "1982": {"year": 1982}, - "19": {"year": 1900}, - "+056789-01": {"year": 56789, "month_of_year": 1, - "expanded_year_digits": 2}, - "-000001-12": {"year": -1, "month_of_year": 12, - "expanded_year_digits": 2}, - "-789123": {"year": -789123, "expanded_year_digits": 2}, - "+450001": {"year": 450001, "expanded_year_digits": 2}, - # The following cannot be parsed - looks like truncated -YYMM. - # "-0023": {"year": -2300, "expanded_year_digits": 2}, - "+5678": {"year": 567800, "expanded_year_digits": 2}, - "1765W04": {"year": 1765, "week_of_year": 4}, - "+001765W44": {"year": 1765, "week_of_year": 44, - "expanded_year_digits": 2}, - "-123321W50": {"year": -123321, "week_of_year": 50, - "expanded_year_digits": 2} - }, - "truncated": { - "-9001": {"year": 90, "month_of_year": 1, - "truncated": True, - "truncated_property": "year_of_century"}, - "960328": {"year": 96, "month_of_year": 3, - "day_of_month": 28, - "truncated": True, - "truncated_property": "year_of_century"}, - "-90": {"year": 90, "truncated": True, - "truncated_property": "year_of_century"}, - "--0501": {"month_of_year": 5, "day_of_month": 1, - "truncated": True}, - "--12": {"month_of_year": 12, "truncated": True}, - "---30": {"day_of_month": 30, "truncated": True}, - "98354": {"year": 98, "day_of_year": 354, "truncated": True, - "truncated_property": "year_of_century"}, - "-034": {"day_of_year": 34, "truncated": True}, - "00W031": {"year": 0, "week_of_year": 3, "day_of_week": 1, - "truncated": True, - "truncated_property": "year_of_century"}, - "99W34": {"year": 99, "week_of_year": 34, "truncated": True, - "truncated_property": "year_of_century"}, - "-1W02": {"year": 1, "week_of_year": 2, - "truncated": True, - "truncated_property": "year_of_decade"}, - "-W031": {"week_of_year": 3, "day_of_week": 1, - "truncated": True}, - "-W32": {"week_of_year": 32, "truncated": True}, - "-W-1": {"day_of_week": 1, "truncated": True} - } - }, - "extended": { - "complete": { - "0044-01-04": {"year": 44, "month_of_year": 1, - "day_of_month": 4}, - "+500200-08-30": {"year": 500200, "month_of_year": 8, - "day_of_month": 30, - "expanded_year_digits": 2}, - "-000056-11-13": {"year": -56, "month_of_year": 11, - "day_of_month": 13, - "expanded_year_digits": 2}, - "-100024-02-10": {"year": -100024, "month_of_year": 2, - "day_of_month": 10, - "expanded_year_digits": 2}, - "1967-056": {"year": 1967, "day_of_year": 56}, - "+123456-078": {"year": 123456, "day_of_year": 78, - "expanded_year_digits": 2}, - "-004560-134": {"year": -4560, "day_of_year": 134, - "expanded_year_digits": 2}, - "1001-W01-1": {"year": 1001, "week_of_year": 1, - "day_of_week": 1}, - "+000001-W45-7": {"year": 1, "week_of_year": 45, - "day_of_week": 7, - "expanded_year_digits": 2}, - "-010001-W05-3": {"year": -10001, "week_of_year": 5, - "day_of_week": 3, - "expanded_year_digits": 2} - }, - "reduced": { - "4401-03": {"year": 4401, "month_of_year": 3}, - "1982": {"year": 1982}, - "19": {"year": 1900}, - "+056789-01": {"year": 56789, "month_of_year": 1, - "expanded_year_digits": 2}, - "-000001-12": {"year": -1, "month_of_year": 12, - "expanded_year_digits": 2}, - "-789123": {"year": -789123, "expanded_year_digits": 2}, - "+450001": {"year": 450001, "expanded_year_digits": 2}, - # The following cannot be parsed - looks like truncated -YYMM. - # "-0023": {"year": -2300, "expanded_year_digits": 2}, - "+5678": {"year": 567800, "expanded_year_digits": 2}, - "1765-W04": {"year": 1765, "week_of_year": 4}, - "+001765-W44": {"year": 1765, "week_of_year": 44, - "expanded_year_digits": 2}, - "-123321-W50": {"year": -123321, "week_of_year": 50, - "expanded_year_digits": 2} - }, - "truncated": { - "-9001": {"year": 90, "month_of_year": 1, - "truncated": True, - "truncated_property": "year_of_century"}, - "96-03-28": {"year": 96, "month_of_year": 3, - "day_of_month": 28, - "truncated": True, - "truncated_property": "year_of_century"}, - "-90": {"year": 90, "truncated": True, - "truncated_property": "year_of_century"}, - "--05-01": {"month_of_year": 5, "day_of_month": 1, - "truncated": True}, - "--12": {"month_of_year": 12, "truncated": True}, - "---30": {"day_of_month": 30, "truncated": True}, - "98-354": {"year": 98, "day_of_year": 354, "truncated": True, - "truncated_property": "year_of_century"}, - "-034": {"day_of_year": 34, "truncated": True}, - "00-W03-1": {"year": 0, "week_of_year": 3, "day_of_week": 1, - "truncated": True, - "truncated_property": "year_of_century"}, - "99-W34": {"year": 99, "week_of_year": 34, "truncated": True, - "truncated_property": "year_of_century"}, - "-1-W02": {"year": 1, "week_of_year": 2, - "truncated": True, - "truncated_property": "year_of_decade"}, - "-W03-1": {"week_of_year": 3, "day_of_week": 1, - "truncated": True}, - "-W32": {"week_of_year": 32, "truncated": True}, - "-W-1": {"day_of_week": 1, "truncated": True} - } - } - } - test_time_map = { - "basic": { - "complete": { - "050102": {"hour_of_day": 5, "minute_of_hour": 1, - "second_of_minute": 2}, - "235902,345": {"hour_of_day": 23, "minute_of_hour": 59, - "second_of_minute": 2, - "second_of_minute_decimal": 0.345}, - "235902.345": {"hour_of_day": 23, "minute_of_hour": 59, - "second_of_minute": 2, - "second_of_minute_decimal": 0.345}, - "1201,4": {"hour_of_day": 12, "minute_of_hour": 1, - "minute_of_hour_decimal": 0.4}, - "1201.4": {"hour_of_day": 12, "minute_of_hour": 1, - "minute_of_hour_decimal": 0.4}, - "00,4356": {"hour_of_day": 0, - "hour_of_day_decimal": 0.4356}, - "00.4356": {"hour_of_day": 0, - "hour_of_day_decimal": 0.4356} - }, - "reduced": { - "0203": {"hour_of_day": 2, "minute_of_hour": 3}, - "17": {"hour_of_day": 17} - }, - "truncated": { - "-5612": {"minute_of_hour": 56, "second_of_minute": 12, - "truncated": True}, - "-12": {"minute_of_hour": 12, "truncated": True}, - "--45": {"second_of_minute": 45, "truncated": True}, - "-1234,45": {"minute_of_hour": 12, "second_of_minute": 34, - "second_of_minute_decimal": 0.45, - "truncated": True}, - "-1234.45": {"minute_of_hour": 12, "second_of_minute": 34, - "second_of_minute_decimal": 0.45, - "truncated": True}, - "-34,2": {"minute_of_hour": 34, "minute_of_hour_decimal": 0.2, - "truncated": True}, - "-34.2": {"minute_of_hour": 34, "minute_of_hour_decimal": 0.2, - "truncated": True}, - "--59,99": {"second_of_minute": 59, - "second_of_minute_decimal": 0.99, - "truncated": True}, - "--59.99": {"second_of_minute": 59, - "second_of_minute_decimal": 0.99, - "truncated": True} - } - }, - "extended": { - "complete": { - "05:01:02": {"hour_of_day": 5, "minute_of_hour": 1, - "second_of_minute": 2}, - "23:59:02,345": {"hour_of_day": 23, "minute_of_hour": 59, - "second_of_minute": 2, - "second_of_minute_decimal": 0.345}, - "23:59:02.345": {"hour_of_day": 23, "minute_of_hour": 59, - "second_of_minute": 2, - "second_of_minute_decimal": 0.345}, - "12:01,4": {"hour_of_day": 12, "minute_of_hour": 1, - "minute_of_hour_decimal": 0.4}, - "12:01.4": {"hour_of_day": 12, "minute_of_hour": 1, - "minute_of_hour_decimal": 0.4}, - "00,4356": {"hour_of_day": 0, "hour_of_day_decimal": 0.4356}, - "00.4356": {"hour_of_day": 0, "hour_of_day_decimal": 0.4356} - }, - "reduced": { - "02:03": {"hour_of_day": 2, "minute_of_hour": 3}, - "17": {"hour_of_day": 17} - }, - "truncated": { - "-56:12": {"minute_of_hour": 56, "second_of_minute": 12, - "truncated": True}, - "-12": {"minute_of_hour": 12, "truncated": True}, - "--45": {"second_of_minute": 45, "truncated": True}, - "-12:34,45": {"minute_of_hour": 12, "second_of_minute": 34, - "second_of_minute_decimal": 0.45, - "truncated": True}, - "-12:34.45": {"minute_of_hour": 12, "second_of_minute": 34, - "second_of_minute_decimal": 0.45, - "truncated": True}, - "-34,2": {"minute_of_hour": 34, "minute_of_hour_decimal": 0.2, - "truncated": True}, - "-34.2": {"minute_of_hour": 34, "minute_of_hour_decimal": 0.2, - "truncated": True}, - "--59,99": {"second_of_minute": 59, - "second_of_minute_decimal": 0.99, - "truncated": True}, - "--59.99": {"second_of_minute": 59, - "second_of_minute_decimal": 0.99, - "truncated": True} - } - } - } - test_time_zone_map = { - "basic": { - "Z": {"time_zone_hour": 0, "time_zone_minute": 0}, - "+01": {"time_zone_hour": 1}, - "-05": {"time_zone_hour": -5}, - "+2301": {"time_zone_hour": 23, "time_zone_minute": 1}, - "-1230": {"time_zone_hour": -12, "time_zone_minute": -30} - }, - "extended": { - "Z": {"time_zone_hour": 0, "time_zone_minute": 0}, - "+01": {"time_zone_hour": 1}, - "-05": {"time_zone_hour": -5}, - "+23:01": {"time_zone_hour": 23, "time_zone_minute": 1}, - "-12:30": {"time_zone_hour": -12, "time_zone_minute": -30} - } - } - format_ok_keys = ["basic", "extended"] - if allow_only_basic: - format_ok_keys = ["basic"] - date_combo_ok_keys = ["complete"] - if allow_truncated: - date_combo_ok_keys = ["complete", "truncated"] - time_combo_ok_keys = ["complete", "reduced"] - time_designator = parser_spec.TIME_DESIGNATOR - for format_type in format_ok_keys: - date_format_tests = test_date_map[format_type] - time_format_tests = test_time_map[format_type] - time_zone_format_tests = test_time_zone_map[format_type] - for date_key in date_format_tests: - if not allow_truncated and date_key == "truncated": - continue - for date_expr, info in date_format_tests[date_key].items(): - yield date_expr, info - for date_key in date_combo_ok_keys: - date_tests = date_format_tests[date_key] - # Add a blank date for time-only testing. - for date_expr, info in date_tests.items(): - for time_key in time_combo_ok_keys: - time_items = time_format_tests[time_key].items() - for time_expr, time_info in time_items: - combo_expr = ( - date_expr + - time_designator + - time_expr - ) - combo_info = {} - for key, value in chain( - info.items(), time_info.items()): - combo_info[key] = value - yield combo_expr, combo_info - if skip_time_zones: - continue - time_zone_items = time_zone_format_tests.items() - for time_zone_expr, time_zone_info in time_zone_items: - tz_expr = combo_expr + time_zone_expr - tz_info = {} - for key, value in \ - chain(combo_info.items(), - time_zone_info.items()): - tz_info[key] = value - yield tz_expr, tz_info - if not allow_truncated: - continue - for time_key in time_format_tests: - time_tests = time_format_tests[time_key] - for time_expr, time_info in time_tests.items(): - combo_expr = ( - time_designator + - time_expr - ) - # Add truncated (no date). - combo_info = {"truncated": True} - for key, value in time_info.items(): - combo_info[key] = value - yield combo_expr, combo_info - if skip_time_zones: - continue - time_zone_items = time_zone_format_tests.items() - for time_zone_expr, time_zone_info in time_zone_items: - tz_expr = combo_expr + time_zone_expr - tz_info = {} - for key, value in \ - chain(combo_info.items(), - time_zone_info.items()): - tz_info[key] = value - yield tz_expr, tz_info - - -def get_truncated_property_tests(): - """ - Tests for largest truncated and - smallest missing property names - """ - test_timepoints = { - "-9001": {"year": 90, - "month_of_year": 1, - "largest_truncated_property_name": "year_of_century", - "smallest_missing_property_name": "century"}, - "20960328": {"year": 96, - "month_of_year": 3, - "day_of_month": 28, - "largest_truncated_property_name": None, - "smallest_missing_property_name": None}, - "-90": {"year": 90, - "largest_truncated_property_name": "year_of_century", - "smallest_missing_property_name": "century"}, - "--0501": {"month_of_year": 5, "day_of_month": 1, - "largest_truncated_property_name": "month_of_year", - "smallest_missing_property_name": "year_of_century"}, - "--12": {"month_of_year": 12, - "largest_truncated_property_name": "month_of_year", - "smallest_missing_property_name": "year_of_century"}, - "---30": {"day_of_month": 30, - "largest_truncated_property_name": "day_of_month", - "smallest_missing_property_name": "month_of_year"}, - "98354": {"year": 98, - "day_of_year": 354, - "largest_truncated_property_name": "year_of_century", - "smallest_missing_property_name": "century"}, - "-034": {"day_of_year": 34, - "largest_truncated_property_name": "day_of_year", - "smallest_missing_property_name": "year_of_century"}, - "00W031": {"year": 0, - "week_of_year": 3, - "day_of_week": 1, - "largest_truncated_property_name": "year_of_century", - "smallest_missing_property_name": "century"}, - "99W34": {"year": 99, - "week_of_year": 34, - "largest_truncated_property_name": "year_of_century", - "smallest_missing_property_name": "century"}, - "-1W02": {"year": 1, - "week_of_year": 2, - "largest_truncated_property_name": "year_of_decade", - "smallest_missing_property_name": "decade_of_century"}, - "-W031": {"week_of_year": 3, - "day_of_week": 1, - "largest_truncated_property_name": "week_of_year", - "smallest_missing_property_name": "year_of_century"}, - "-W32": {"week_of_year": 32, - "largest_truncated_property_name": "week_of_year", - "smallest_missing_property_name": "year_of_century"}, - "-W-1": {"day_of_week": 1, - "largest_truncated_property_name": "day_of_week", - "smallest_missing_property_name": "week_of_year"}, - "T04:30": {"hour_of_day": 4, - "minute_of_hour": 30, - "largest_truncated_property_name": "hour_of_day", - "smallest_missing_property_name": "day_of_month"}, - "T19": {"hour_of_day": 19, - "largest_truncated_property_name": "hour_of_day", - "smallest_missing_property_name": "day_of_month"}, - "T-56:12": {"minute_of_hour": 56, - "second_of_minute": 12, - "largest_truncated_property_name": "minute_of_hour", - "smallest_missing_property_name": "hour_of_day"}, - "T-12": {"minute_of_hour": 12, - "largest_truncated_property_name": "minute_of_hour", - "smallest_missing_property_name": "hour_of_day"}, - "T--45": {"second_of_minute": 45, - "largest_truncated_property_name": "second_of_minute", - "smallest_missing_property_name": "minute_of_hour"}, - "T-12:34.45": {"minute_of_hour": 12, - "second_of_minute": 34, - "second_of_minute_decimal": 0.45, - "largest_truncated_property_name": "minute_of_hour", - "smallest_missing_property_name": "hour_of_day"}, - "T-34,2": {"minute_of_hour": 34, - "minute_of_hour_decimal": 0.2, - "largest_truncated_property_name": "minute_of_hour", - "smallest_missing_property_name": "hour_of_day"}, - "T--59.99": {"second_of_minute": 59, - "second_of_minute_decimal": 0.99, - "largest_truncated_property_name": "second_of_minute", - "smallest_missing_property_name": "minute_of_hour"}} - return test_timepoints - - -def get_timepoint_subtract_tests(): - """Yield tests for subtracting one timepoint from another.""" - return [ - ( - {"year": 44, "month_of_year": 1, "day_of_month": 4, - "hour_of_day": 5, "minute_of_hour": 1, "second_of_minute": 2, - "time_zone_hour": 0, "time_zone_minute": 0}, - {"year": 41, "month_of_year": 12, "day_of_month": 2, - "hour_of_day": 4, "minute_of_hour": 23, "second_of_minute": 1, - "time_zone_hour": 3, "time_zone_minute": 20}, - "P763DT3H58M1S" - ), - ( - {"year": 41, "month_of_year": 12, "day_of_month": 2, - "hour_of_day": 4, "minute_of_hour": 23, "second_of_minute": 1, - "time_zone_hour": 3, "time_zone_minute": 20}, - {"year": 44, "month_of_year": 1, "day_of_month": 4, - "hour_of_day": 5, "minute_of_hour": 1, "second_of_minute": 2, - "time_zone_hour": 0, "time_zone_minute": 0}, - "-P763DT3H58M1S" - ), - ( - {"year": 1991, "month_of_year": 6, "day_of_month": 3, - "hour_of_day": 0, "time_zone_hour": 0, "time_zone_minute": 0}, - {"year": 1991, "month_of_year": 5, "day_of_month": 4, - "hour_of_day": 5, "time_zone_hour": 0, "time_zone_minute": 0}, - "P29DT19H" - ), - ( - {"year": 1969, "month_of_year": 7, "day_of_month": 20, - "hour_of_day": 20, "time_zone_hour": 0, "time_zone_minute": 0}, - {"year": 1969, "month_of_year": 7, "day_of_month": 20, - "hour_of_day": 19, "time_zone_hour": 0, "time_zone_minute": 0}, - "PT1H" - ), - - ( - {"year": 1969, "month_of_year": 7, "day_of_month": 20, - "hour_of_day": 19, "time_zone_hour": 0, "time_zone_minute": 0}, - {"year": 1969, "month_of_year": 7, "day_of_month": 20, - "hour_of_day": 20, "time_zone_hour": 0, "time_zone_minute": 0}, - "-PT1H" - ), - ( - {"year": 1991, "month_of_year": 5, "day_of_month": 4, - "hour_of_day": 5, "time_zone_hour": 0, "time_zone_minute": 0}, - {"year": 1991, "month_of_year": 6, "day_of_month": 3, - "hour_of_day": 0, "time_zone_hour": 0, "time_zone_minute": 0}, - "-P29DT19H" - ), - ( - {"year": 2014, "month_of_year": 1, "day_of_month": 1, - "hour_of_day": 0, "time_zone_hour": 0, "time_zone_minute": 0}, - {"year": 2013, "month_of_year": 12, "day_of_month": 31, - "hour_of_day": 23, "time_zone_hour": 0, "time_zone_minute": 0}, - "PT1H" - ), - ( - {"year": 2013, "month_of_year": 12, "day_of_month": 31, - "hour_of_day": 23, "time_zone_hour": 0, "time_zone_minute": 0}, - {"year": 2014, "month_of_year": 1, "day_of_month": 1, - "hour_of_day": 0, "time_zone_hour": 0, "time_zone_minute": 0}, - "-PT1H" - ), - ( - {"year": 2014, "month_of_year": 1, "day_of_month": 1, - "hour_of_day": 0, "time_zone_hour": 0, "time_zone_minute": 0}, - {"year": 2013, "month_of_year": 12, "day_of_month": 1, - "hour_of_day": 0, "time_zone_hour": 0, "time_zone_minute": 0}, - "P31D" - ), - ( - {"year": 2013, "month_of_year": 12, "day_of_month": 1, - "hour_of_day": 0, "time_zone_hour": 0, "time_zone_minute": 0}, - {"year": 2014, "month_of_year": 1, "day_of_month": 1, - "hour_of_day": 0, "time_zone_hour": 0, "time_zone_minute": 0}, - "-P31D" - ), - ( - {"year": 44, "month_of_year": 1, "day_of_month": 4, - "hour_of_day": 5, "minute_of_hour": 1, "second_of_minute": 2, - "time_zone_hour": 0, "time_zone_minute": 0}, - {"year": 41, "month_of_year": 12, "day_of_month": 2, - "hour_of_day": 13, "minute_of_hour": 23, "second_of_minute": 1, - "time_zone_hour": 3, "time_zone_minute": 20}, - "P762DT18H58M1S" - ), - ( - {"year": 41, "month_of_year": 12, "day_of_month": 2, - "hour_of_day": 13, "minute_of_hour": 23, "second_of_minute": 1, - "time_zone_hour": 3, "time_zone_minute": 20}, - {"year": 44, "month_of_year": 1, "day_of_month": 4, - "hour_of_day": 5, "minute_of_hour": 1, "second_of_minute": 2, - "time_zone_hour": 0, "time_zone_minute": 0}, - "-P762DT18H58M1S" - ), - ] - - -def get_duration_subtract_tests(): - """Yield tests for subtracting a duration from a timepoint.""" - return [ - ( - {"year": 2010, "day_of_year": 65, - # "month_of_year": 3, "day_of_month": 6, - "hour_of_day": 12, "minute_of_hour": 0, "second_of_minute": 0, - "time_zone_hour": 0, "time_zone_minute": 0}, - "P6Y", - {"year": 2004, # "day_of_year": 65, - "month_of_year": 3, "day_of_month": 5, - "hour_of_day": 12, "minute_of_hour": 0, "second_of_minute": 0, - "time_zone_hour": 0, "time_zone_minute": 0} - ), - ( - {"year": 2010, "week_of_year": 10, "day_of_week": 3, - # "month_of_year": 3, "day_of_month": 10, - "hour_of_day": 12, "minute_of_hour": 0, "second_of_minute": 0, - "time_zone_hour": 0, "time_zone_minute": 0}, - "P6Y", - {"year": 2004, # "week_of_year": 10, "day_of_week": 3, - "month_of_year": 3, "day_of_month": 3, - "hour_of_day": 12, "minute_of_hour": 0, "second_of_minute": 0, - "time_zone_hour": 0, "time_zone_minute": 0} - ), - ] - - -def get_timerecurrence_expansion_tests(): - """Return test expansion expressions for data.TimeRecurrence.""" - return [ - ("R3/1001-W01-1T00:00:00Z/1002-W52-6T00:00:00-05:30", - ["1001-W01-1T00:00:00Z", "1001-W53-3T14:45:00Z", - "1002-W52-6T05:30:00Z"]), - ("R3/P700D/1957-W01-1T06,5Z", - ["1953-W10-1T06,5Z", "1955-W05-1T06,5Z", "1957-W01-1T06,5Z"]), - ("R3/P5DT2,5S/1001-W11-1T00:30:02,5-02:00", - ["1001-W09-5T00:29:57,5-02:00", "1001-W10-3T00:30:00-02:00", - "1001-W11-1T00:30:02,5-02:00"]), - ("R/+000001W457T060000Z/P4M1D", - ["+000001-W45-7T06:00:00Z", "+000002-W11-2T06:00:00Z", - "+000002-W28-6T06:00:00Z"]), - ("R/P4M1DT6M/+002302-002T06:00:00-00:30", - ["+002302-002T06:00:00-00:30", "+002301-244T05:54:00-00:30", - "+002301-120T05:48:00-00:30"]), - ("R/P30Y2DT15H/-099994-02-12T17:00:00-02:30", - ["-099994-02-12T17:00:00-02:30", "-100024-02-10T02:00:00-02:30", - "-100054-02-07T11:00:00-02:30"]), - ("R/-100024-02-10T17:00:00-12:30/PT5.5H", - ["-100024-02-10T17:00:00-12:30", "-100024-02-10T22:30:00-12:30", - "-100024-02-11T04:00:00-12:30"]) - ] - - -def get_timerecurrence_expansion_tests_for_alt_calendar(calendar_mode): - """Return alternate calendar tests for data.TimeRecurrence.""" - if calendar_mode == "360": - return get_timerecurrence_expansion_tests_360() - if calendar_mode == "365": - return get_timerecurrence_expansion_tests_365() - if calendar_mode == "366": - return get_timerecurrence_expansion_tests_366() - - -def get_timerecurrence_expansion_tests_360(): - """Return test expansion expressions for data.TimeRecurrence.""" - return [ - ("R13/1984-01-30T00Z/P1M", - ["1984-01-30T00:00:00Z", "1984-02-30T00:00:00Z", - "1984-03-30T00:00:00Z", "1984-04-30T00:00:00Z", - "1984-05-30T00:00:00Z", "1984-06-30T00:00:00Z", - "1984-07-30T00:00:00Z", "1984-08-30T00:00:00Z", - "1984-09-30T00:00:00Z", "1984-10-30T00:00:00Z", - "1984-11-30T00:00:00Z", "1984-12-30T00:00:00Z", - "1985-01-30T00:00:00Z"]), - ("R2/1984-01-30T00Z/P1D", - ["1984-01-30T00:00:00Z", "1984-02-01T00:00:00Z"]), - ("R2/P1D/1984-02-01T00Z", - ["1984-01-30T00:00:00Z", "1984-02-01T00:00:00Z"]), - ("R2/P1D/1984-01-01T00Z", - ["1983-12-30T00:00:00Z", "1984-01-01T00:00:00Z"]), - ("R2/1983-12-30T00Z/P1D", - ["1983-12-30T00:00:00Z", "1984-01-01T00:00:00Z"]), - ("R2/P1D/2005-01-01T00Z", - ["2004-12-30T00:00:00Z", "2005-01-01T00:00:00Z"]), - ("R2/2003-12-30T00Z/P1D", - ["2003-12-30T00:00:00Z", "2004-01-01T00:00:00Z"]), - ("R2/P1D/2004-01-01T00Z", - ["2003-12-30T00:00:00Z", "2004-01-01T00:00:00Z"]), - ("R2/2004-12-30T00Z/P1D", - ["2004-12-30T00:00:00Z", "2005-01-01T00:00:00Z"]), - ("R3/P1Y/2005-02-30T00Z", - ["2003-02-30T00:00:00Z", "2004-02-30T00:00:00Z", - "2005-02-30T00:00:00Z"]), - ("R3/2003-02-30T00Z/P1Y", - ["2003-02-30T00:00:00Z", "2004-02-30T00:00:00Z", - "2005-02-30T00:00:00Z"]), - ] - - -def get_timerecurrence_expansion_tests_365(): - """Return test expansion expressions for data.TimeRecurrence.""" - return [ - ("R13/1984-01-30T00Z/P1M", - ["1984-01-30T00:00:00Z", "1984-02-28T00:00:00Z", - "1984-03-28T00:00:00Z", "1984-04-28T00:00:00Z", - "1984-05-28T00:00:00Z", "1984-06-28T00:00:00Z", - "1984-07-28T00:00:00Z", "1984-08-28T00:00:00Z", - "1984-09-28T00:00:00Z", "1984-10-28T00:00:00Z", - "1984-11-28T00:00:00Z", "1984-12-28T00:00:00Z", - "1985-01-28T00:00:00Z"]), - ("R13/1985-01-30T00Z/P1M", - ["1985-01-30T00:00:00Z", "1985-02-28T00:00:00Z", - "1985-03-28T00:00:00Z", "1985-04-28T00:00:00Z", - "1985-05-28T00:00:00Z", "1985-06-28T00:00:00Z", - "1985-07-28T00:00:00Z", "1985-08-28T00:00:00Z", - "1985-09-28T00:00:00Z", "1985-10-28T00:00:00Z", - "1985-11-28T00:00:00Z", "1985-12-28T00:00:00Z", - "1986-01-28T00:00:00Z"]), - ("R2/1984-01-30T00Z/P1D", - ["1984-01-30T00:00:00Z", "1984-01-31T00:00:00Z"]), - ("R2/P1D/1984-02-01T00Z", - ["1984-01-31T00:00:00Z", "1984-02-01T00:00:00Z"]), - ("R2/P1D/1984-01-01T00Z", - ["1983-12-31T00:00:00Z", "1984-01-01T00:00:00Z"]), - ("R2/1983-12-30T00Z/P1D", - ["1983-12-30T00:00:00Z", "1983-12-31T00:00:00Z"]), - ("R2/2000-02-28T00Z/P1Y1D", - ["2000-02-28T00:00:00Z", "2001-03-01T00:00:00Z"]), - ("R2/2001-02-28T00Z/P1Y1D", - ["2001-02-28T00:00:00Z", "2002-03-01T00:00:00Z"]), - ] - - -def get_timerecurrence_expansion_tests_366(): - """Return test expansion expressions for data.TimeRecurrence.""" - return [ - ("R13/1984-01-30T00Z/P1M", - ["1984-01-30T00:00:00Z", "1984-02-29T00:00:00Z", - "1984-03-29T00:00:00Z", "1984-04-29T00:00:00Z", - "1984-05-29T00:00:00Z", "1984-06-29T00:00:00Z", - "1984-07-29T00:00:00Z", "1984-08-29T00:00:00Z", - "1984-09-29T00:00:00Z", "1984-10-29T00:00:00Z", - "1984-11-29T00:00:00Z", "1984-12-29T00:00:00Z", - "1985-01-29T00:00:00Z"]), - ("R13/1985-01-30T00Z/P1M", - ["1985-01-30T00:00:00Z", "1985-02-29T00:00:00Z", - "1985-03-29T00:00:00Z", "1985-04-29T00:00:00Z", - "1985-05-29T00:00:00Z", "1985-06-29T00:00:00Z", - "1985-07-29T00:00:00Z", "1985-08-29T00:00:00Z", - "1985-09-29T00:00:00Z", "1985-10-29T00:00:00Z", - "1985-11-29T00:00:00Z", "1985-12-29T00:00:00Z", - "1986-01-29T00:00:00Z"]), - ("R2/1984-01-30T00Z/P1D", - ["1984-01-30T00:00:00Z", "1984-01-31T00:00:00Z"]), - ("R2/P1D/1984-02-01T00Z", - ["1984-01-31T00:00:00Z", "1984-02-01T00:00:00Z"]), - ("R2/P1D/1984-01-01T00Z", - ["1983-12-31T00:00:00Z", "1984-01-01T00:00:00Z"]), - ("R2/1983-12-30T00Z/P1D", - ["1983-12-30T00:00:00Z", "1983-12-31T00:00:00Z"]), - ("R2/1999-02-28T00Z/P1Y1D", - ["1999-02-28T00:00:00Z", "2000-02-29T00:00:00Z"]), - ("R2/2000-02-28T00Z/P1Y1D", - ["2000-02-28T00:00:00Z", "2001-02-29T00:00:00Z"]), - ("R2/2001-02-28T00Z/P1Y1D", - ["2001-02-28T00:00:00Z", "2002-02-29T00:00:00Z"]), - ] - - -def get_timerecurrence_membership_tests(): - """Return test membership expressions for data.TimeRecurrence.""" - return [ - ("R3/1001-W01-1T00:00:00Z/1002-W52-6T00:00:00-05:30", - [("1001-W01-1T00:00:00Z", True), - ("1000-12-29T00:00:00Z", True), - ("0901-07-08T12:45:00Z", False), - ("1001-W01-2T00:00:00Z", False), - ("1001-W53-3T14:45:00Z", True), - ("1002-W52-6T05:30:00Z", True), - ("1002-W52-6T03:30:00-02:00", True), - ("1002-W52-6T07:30:00+02:00", True), - ("10030101T00Z", False)]), - ("R3/P700D/1957-W01-1T06,5Z", - [("1953-W10-1T06,5Z", True), - ("1953-03-02T06,5Z", True), - ("1952-03-02T06,5Z", False), - ("1955-W05-1T06,5Z", True), - ("1957-W01-1T06,5Z", True), - ("1956-366T06,5Z", True), - ("1956-356T04,5Z", False)]), - ] - - -def get_timerecurrenceparser_tests(): - """Yield tests for the time recurrence parser.""" - test_points = ["-100024-02-10T17:00:00-12:30", - "+000001-W45-7T06Z", "1001W011", - "1955W051T06,5Z", "1999-06-01", - "1967-056", "+5002000830T235902,345", - "1765-W04"] - for reps in [None, 1, 2, 3, 10]: - if reps is None: - reps_string = "" - else: - reps_string = str(reps) - point_parser = parsers.TimePointParser() - duration_parser = parsers.DurationParser() - for point_expr in test_points: - duration_tests = get_timedurationparser_tests() - start_point = point_parser.parse(point_expr) - for duration_expr, _ in duration_tests: - if duration_expr.startswith("-P"): - # Our negative durations are not supported in recurrences. - continue - duration = duration_parser.parse(duration_expr) - end_point = start_point + duration - if reps is not None: - expr_1 = ("R" + reps_string + "/" + str(start_point) + - "/" + str(end_point)) - yield expr_1, {"repetitions": reps, - "start_point": start_point, - "end_point": end_point} - expr_3 = ("R" + reps_string + "/" + str(start_point) + - "/" + str(duration)) - yield expr_3, {"repetitions": reps, - "start_point": start_point, - "duration": duration} - expr_4 = ("R" + reps_string + "/" + str(duration) + "/" + - str(end_point)) - yield expr_4, {"repetitions": reps, "duration": duration, - "end_point": end_point} - - -def get_local_time_zone_hours_minutes(): - """Provide an independent method of getting the local time zone.""" - utc_offset = datetime.datetime.now() - datetime.datetime.utcnow() - # datetime.timedelta represents -21 microseconds as -1 day, - # +86399 seconds, +999979 microseconds. This is not nice. - utc_offset_seconds = utc_offset.seconds + 86400 * utc_offset.days - utc_offset_hours = (utc_offset_seconds + 1800) // 3600 - utc_offset_minutes = ( - ((utc_offset_seconds - 3600 * utc_offset_hours) + 30) // 60 - ) - return utc_offset_hours, utc_offset_minutes - - -class TestSuite(unittest.TestCase): - """Test the functionality of parsers and data model manipulation.""" - - @pytest.mark.slow - def test_days_in_year_range(self): - """Test the summing-over-days-in-year-range shortcut code.""" - for start_year in range(-401, 2): - for end_year in range(start_year, 2): - test_days = data.get_days_in_year_range( - start_year, end_year) - control_days = 0 - for year in range(start_year, end_year + 1): - control_days += data.get_days_in_year(year) - self.assertEqual( - control_days, test_days, "days in %s to %s" % ( - start_year, end_year) - ) - - def test_largest_truncated_property_name(self): - """Test the largest truncated property name.""" - - parser = parsers.TimePointParser( - allow_truncated=True) - - truncated_property_tests = get_truncated_property_tests() - for expression in truncated_property_tests.keys(): - try: - test_data = parser.parse(expression) - except parsers.ISO8601SyntaxError as syn_exc: - raise ValueError("Parsing failed for {0}: {1}".format( - expression, syn_exc)) - - self.assertEqual( - test_data.get_largest_truncated_property_name(), - truncated_property_tests[expression] - ["largest_truncated_property_name"], - msg=expression) - - def test_smallest_missing_property_name(self): - """Test the smallest missing property name.""" - - parser = parsers.TimePointParser( - allow_truncated=True) - - truncated_property_tests = get_truncated_property_tests() - for expression in truncated_property_tests.keys(): - try: - test_data = parser.parse(expression) - except parsers.ISO8601SyntaxError as syn_exc: - raise ValueError("Parsing failed for {0}: {1}".format( - expression, syn_exc)) - - self.assertEqual( - test_data.get_smallest_missing_property_name(), - truncated_property_tests[expression] - ["smallest_missing_property_name"], - msg=expression) - - def test_timeduration(self): - """Test the duration class methods.""" - for test_props, method, method_args, ctrl_results in ( - get_timeduration_tests()): - duration = data.Duration(**test_props) - duration_method = getattr(duration, method) - test_results = duration_method(*method_args) - self.assertEqual( - test_results, ctrl_results, - "%s -> %s(%s)" % (test_props, method, method_args) - ) - - def test_timeduration_parser(self): - """Test the duration parsing.""" - parser = parsers.DurationParser() - for expression, ctrl_result in get_timedurationparser_tests(): - try: - test_result = str(parser.parse(expression)) - except parsers.ISO8601SyntaxError: - raise ValueError( - "DurationParser test failed to parse '%s'" % - expression - ) - self.assertEqual(test_result, ctrl_result, expression) - - def test_timeduration_dumper(self): - """Test the duration dumping.""" - for ctrl_expression, test_props in get_timedurationdumper_tests(): - duration = data.Duration(**test_props) - test_expression = str(duration) - self.assertEqual(test_expression, ctrl_expression, - str(test_props)) - - def test_timeduration_add_week(self): - """Test the duration not in weeks add duration in weeks.""" - self.assertEqual( - str(data.Duration(days=7) + data.Duration(weeks=1)), - "P14D") - - def test_timepoint_plus_float_time_duration_day_of_month_type(self): - """Test (TimePoint + Duration).day_of_month is an int.""" - time_point = data.TimePoint(year=2000) + data.Duration(seconds=1.0) - self.assertEqual(type(time_point.day_of_month), int) - - def test_timepoint_subtract(self): - """Test subtracting one time point from another.""" - for test_props1, test_props2, ctrl_string in ( - get_timepoint_subtract_tests()): - point1 = data.TimePoint(**test_props1) - point2 = data.TimePoint(**test_props2) - test_string = str(point1 - point2) - self.assertEqual(test_string, ctrl_string, - "%s - %s" % (point1, point2)) - - def test_duration_subtract(self): - """Test subtracting a duration from a timepoint.""" - parser = parsers.DurationParser() - for my_timepoint, my_duration, my_result in ( - get_duration_subtract_tests()): - start_point = data.TimePoint(**my_timepoint) - test_duration = parser.parse(my_duration) - end_point = data.TimePoint(**my_result) - test_subtract = (start_point - test_duration).to_calendar_date() - self.assertEqual(str(test_subtract), str(end_point), - "%s - %s" % (start_point, test_duration)) - - def test_timepoint_time_zone(self): - """Test the time zone handling of timepoint instances.""" - year = 2000 - month_of_year = 1 - day_of_month = 1 - utc_offset_hours, utc_offset_minutes = ( - get_local_time_zone_hours_minutes() - ) - for hour_of_day in range(24): - for minute_of_hour in [0, 30]: - test_dates = [ - data.TimePoint( - year=year, - month_of_year=month_of_year, - day_of_month=day_of_month, - hour_of_day=hour_of_day, - minute_of_hour=minute_of_hour - ) - ] - test_dates.append(test_dates[0].copy()) - test_dates.append(test_dates[0].copy()) - test_dates.append(test_dates[0].copy()) - test_dates[0].set_time_zone_to_utc() - self.assertEqual(test_dates[0].time_zone.hours, 0, - test_dates[0]) - self.assertEqual(test_dates[0].time_zone.minutes, 0, - test_dates[0]) - test_dates[1].set_time_zone_to_local() - self.assertEqual(test_dates[1].time_zone.hours, - utc_offset_hours, test_dates[1]) - - self.assertEqual(test_dates[1].time_zone.minutes, - utc_offset_minutes, test_dates[1]) - test_dates[2].set_time_zone( - data.TimeZone(hours=-13, minutes=-45)) - - test_dates[3].set_time_zone( - data.TimeZone(hours=8, minutes=30)) - for i_test_date in list(test_dates): - i_test_date_str = str(i_test_date) - date_no_tz = i_test_date.copy() - date_no_tz.time_zone = data.TimeZone(hours=0, minutes=0) - if (i_test_date.time_zone.hours >= 0 or - i_test_date.time_zone.minutes >= 0): - utc_offset = date_no_tz - i_test_date - else: - utc_offset = (i_test_date - date_no_tz) * -1 - self.assertEqual(utc_offset.hours, - i_test_date.time_zone.hours, - i_test_date_str + " utc offset (hrs)") - self.assertEqual(utc_offset.minutes, - i_test_date.time_zone.minutes, - i_test_date_str + " utc offset (mins)") - for j_test_date in list(test_dates): - j_test_date_str = str(j_test_date) - self.assertEqual( - i_test_date, j_test_date, - i_test_date_str + " == " + j_test_date_str) - duration = j_test_date - i_test_date - self.assertEqual( - duration, data.Duration(days=0), - i_test_date_str + " - " + j_test_date_str) - - def test_timepoint_dumper(self): - """Test the dumping of TimePoint instances.""" - parser = parsers.TimePointParser(allow_truncated=True, - default_to_unknown_time_zone=True) - dumper = dumpers.TimePointDumper() - for expression, timepoint_kwargs in get_timepointparser_tests( - allow_truncated=True): - ctrl_timepoint = data.TimePoint(**timepoint_kwargs) - try: - test_timepoint = parser.parse(str(ctrl_timepoint)) - except parsers.ISO8601SyntaxError as syn_exc: - raise ValueError( - "Parsing failed for the dump of {0}: {1}".format( - expression, syn_exc)) - self.assertEqual(test_timepoint, - ctrl_timepoint, expression) - for timepoint_kwargs, format_results in ( - get_timepoint_dumper_tests()): - ctrl_timepoint = data.TimePoint(**timepoint_kwargs) - for format_, ctrl_data in format_results: - test_data = dumper.dump(ctrl_timepoint, format_) - self.assertEqual(test_data, ctrl_data, format_) - for timepoint_kwargs, format_exception_results in ( - get_timepointdumper_failure_tests()): - ctrl_timepoint = data.TimePoint(**timepoint_kwargs) - for format_, ctrl_exception, num_expanded_year_digits in ( - format_exception_results): - dumper = dumpers.TimePointDumper( - num_expanded_year_digits=num_expanded_year_digits) - self.assertRaises(ctrl_exception, dumper.dump, - ctrl_timepoint, format_) - value_error_timepoint = data.TimePoint(minute_of_hour=10) - value_error_timepoint.minute_of_hour = "1O" - self.assertRaises(ValueError, dumper.dump, value_error_timepoint, "%M") - - def test_timepoint_dumper_bounds_error_message(self): - """Test the exception text contains the information expected""" - the_error = dumpers.TimePointDumperBoundsError("TimePoint1", "year", - 10, 20) - the_string = the_error.__str__() - self.assertTrue("TimePoint1" in the_string, - "Failed to find TimePoint1 in {}".format(the_string)) - self.assertTrue("year" in the_string, - "Failed to find TimePoint1 in {}".format(the_string)) - self.assertTrue("10" in the_string, - "Failed to find TimePoint1 in {}".format(the_string)) - self.assertTrue("20" in the_string, - "Failed to find TimePoint1 in {}".format(the_string)) - - get_test_timepoint_dumper_get_time_zone = [ - ["+250:00", None], - ["+25:00", (25, 0)], - ["+12:00", (12, 0)], - ["+12:45", (12, 45)], - ["+01:00", (1, 0)], - ["Z", (0, 0)], - ["-03:00", (-3, 0)], - ["-03:30", (-3, -30)], - ["-11:00", (-11, 0)], - ["+00:00", (0, 0)], - ["-00:00", (0, 0)] - ] - - def test_timepoint_dumper_get_time_zone(self): - """Test the time zone returned by TimerPointDumper.get_time_zone""" - dumper = dumpers.TimePointDumper(num_expanded_year_digits=2) - for value, expected in self.get_test_timepoint_dumper_get_time_zone: - tz = dumper.get_time_zone(value) - self.assertEqual(expected, tz) - - def test_timepoint_dumper_after_copy(self): - """Test that printing the TimePoint attributes works after it has - been copied, see issue #102 for more information""" - time_point = data.TimePoint(year=2000, truncated=True, - truncated_dump_format='CCYY') - the_copy = time_point.copy() - self.assertEqual(str(time_point), str(the_copy)) - - def test_timepoint_parser(self): - """Test the parsing of date/time expressions.""" - - # Test unknown time zone assumptions. - parser = parsers.TimePointParser( - allow_truncated=True, - default_to_unknown_time_zone=True) - for expression, timepoint_kwargs in get_timepointparser_tests( - allow_truncated=True): - timepoint_kwargs = copy.deepcopy(timepoint_kwargs) - try: - test_data = str(parser.parse(expression)) - except parsers.ISO8601SyntaxError as syn_exc: - raise ValueError("Parsing failed for {0}: {1}".format( - expression, syn_exc)) - ctrl_data = str(data.TimePoint(**timepoint_kwargs)) - self.assertEqual(test_data, ctrl_data, expression) - ctrl_data = expression - test_data = str(parser.parse(expression, dump_as_parsed=True)) - self.assertEqual(test_data, ctrl_data, expression) - - # Test local time zone assumptions (the default). - utc_offset_hours, utc_offset_minutes = ( - get_local_time_zone_hours_minutes() - ) - parser = parsers.TimePointParser(allow_truncated=True) - for expression, timepoint_kwargs in get_timepointparser_tests( - allow_truncated=True, skip_time_zones=True): - timepoint_kwargs = copy.deepcopy(timepoint_kwargs) - try: - test_timepoint = parser.parse(expression) - except parsers.ISO8601SyntaxError as syn_exc: - raise ValueError("Parsing failed for {0}: {1}".format( - expression, syn_exc)) - test_data = (test_timepoint.time_zone.hours, - test_timepoint.time_zone.minutes) - ctrl_data = (utc_offset_hours, utc_offset_minutes) - self.assertEqual(test_data, ctrl_data, - "Local time zone for " + expression) - - # Test given time zone assumptions. - utc_offset_hours, utc_offset_minutes = ( - get_local_time_zone_hours_minutes() - ) - given_utc_offset_hours = -2 # This is an arbitrary number! - if given_utc_offset_hours == utc_offset_hours: - # No point testing this twice, change it. - given_utc_offset_hours = -3 - given_utc_offset_minutes = -15 - given_time_zone_hours_minutes = ( - given_utc_offset_hours, given_utc_offset_minutes) - parser = parsers.TimePointParser( - allow_truncated=True, - assumed_time_zone=given_time_zone_hours_minutes - ) - for expression, timepoint_kwargs in get_timepointparser_tests( - allow_truncated=True, skip_time_zones=True): - timepoint_kwargs = copy.deepcopy(timepoint_kwargs) - try: - test_timepoint = parser.parse(expression) - except parsers.ISO8601SyntaxError as syn_exc: - raise ValueError("Parsing failed for {0}: {1}".format( - expression, syn_exc)) - test_data = (test_timepoint.time_zone.hours, - test_timepoint.time_zone.minutes) - ctrl_data = given_time_zone_hours_minutes - self.assertEqual(test_data, ctrl_data, - "A given time zone for " + expression) - - # Test UTC time zone assumptions. - parser = parsers.TimePointParser( - allow_truncated=True, - assumed_time_zone=(0, 0) - ) - for expression, timepoint_kwargs in get_timepointparser_tests( - allow_truncated=True, skip_time_zones=True): - timepoint_kwargs = copy.deepcopy(timepoint_kwargs) - try: - test_timepoint = parser.parse(expression) - except parsers.ISO8601SyntaxError as syn_exc: - raise ValueError("Parsing failed for {0}: {1}".format( - expression, syn_exc)) - test_data = (test_timepoint.time_zone.hours, - test_timepoint.time_zone.minutes) - ctrl_data = (0, 0) - self.assertEqual(test_data, ctrl_data, - "UTC for " + expression) - - def test_timepoint_strftime_strptime(self): - """Test the strftime/strptime for date/time expressions.""" - parser = parsers.TimePointParser() - parse_tokens = list(parser_spec.STRFTIME_TRANSLATE_INFO.keys()) - parse_tokens.remove("%z") # Don't test datetime's tz handling. - format_string = "" - for i, token in enumerate(parse_tokens): - format_string += token - if i % 2 == 0: - format_string += " " - if i % 3 == 0: - format_string += ":" - if i % 5 == 0: - format_string += "?foobar" - if i % 7 == 0: - format_string += "++(" - strftime_string = format_string - strptime_strings = [format_string] - for key in parser_spec.STRPTIME_EXCLUSIVE_GROUP_INFO.keys(): - strptime_strings[-1] = strptime_strings[-1].replace(key, "") - strptime_strings.append(format_string) - for values in parser_spec.STRPTIME_EXCLUSIVE_GROUP_INFO.values(): - for value in values: - strptime_strings[-1] = strptime_strings[-1].replace(value, "") - ctrl_date = datetime.datetime(2002, 3, 1, 12, 30, 2) - - # Test %z dumping. - for sign in [1, -1]: - for hour in range(0, 24): - for minute in range(0, 59): - if hour == 0 and minute == 0 and sign == -1: - # -0000, same as +0000, but invalid. - continue - test_date = data.TimePoint( - year=ctrl_date.year, - month_of_year=ctrl_date.month, - day_of_month=ctrl_date.day, - hour_of_day=ctrl_date.hour, - minute_of_hour=ctrl_date.minute, - second_of_minute=ctrl_date.second, - time_zone_hour=sign * hour, - time_zone_minute=sign * minute - ) - ctrl_string = "-" if sign == -1 else "+" - ctrl_string += "%02d%02d" % (hour, minute) - self.assertEqual(test_date.strftime("%z"), - ctrl_string, - "%z for " + str(test_date)) - - test_date = data.TimePoint( - year=ctrl_date.year, - month_of_year=ctrl_date.month, - day_of_month=ctrl_date.day, - hour_of_day=ctrl_date.hour, - minute_of_hour=ctrl_date.minute, - second_of_minute=ctrl_date.second - ) - for test_date in [test_date, test_date.copy().to_week_date(), - test_date.copy().to_ordinal_date()]: - ctrl_data = ctrl_date.strftime(strftime_string) - test_data = test_date.strftime(strftime_string) - self.assertEqual(test_data, ctrl_data, strftime_string) - for strptime_string in strptime_strings: - ctrl_dump = ctrl_date.strftime(strptime_string) - test_dump = test_date.strftime(strptime_string) - self.assertEqual(test_dump, ctrl_dump, strptime_string) - if "%s" in strptime_string: - # The datetime library can't handle this for strptime! - ctrl_data = ctrl_date - else: - ctrl_data = datetime.datetime.strptime( - ctrl_dump, strptime_string) - test_data = parser.strptime(test_dump, strptime_string) - - ctrl_data = ( - ctrl_data.year, ctrl_data.month, ctrl_data.day, - ctrl_data.hour, ctrl_data.minute, ctrl_data.second - ) - test_data = tuple(list(test_data.get_calendar_date()) + - list(test_data.get_hour_minute_second())) - if "%y" in strptime_string: - # %y is the decadal year (00 to 99) within a century. - # The datetime library, for some reason, sets a default - # century of '2000' - so nuke this extra information. - ctrl_data = tuple([ctrl_data[0] % 100] + - list(ctrl_data[1:])) - self.assertEqual(test_data, ctrl_data, test_dump + "\n" + - strptime_string) - - def test_timerecurrence_alt_calendars(self): - """Test recurring date/time series for alternate calendars.""" - for calendar_mode in ["360", "365", "366"]: - data.CALENDAR.set_mode(calendar_mode + "day") - self.assertEqual( - data.CALENDAR.mode, - getattr(data.Calendar, "MODE_%s" % calendar_mode) - ) - parser = parsers.TimeRecurrenceParser() - tests = get_timerecurrence_expansion_tests_for_alt_calendar( - calendar_mode) - for expression, ctrl_results in tests: - try: - test_recurrence = parser.parse(expression) - except parsers.ISO8601SyntaxError: - raise ValueError( - "TimeRecurrenceParser test failed to parse '%s'" % - expression - ) - test_results = [] - for time_point in test_recurrence: - test_results.append(str(time_point)) - self.assertEqual(test_results, ctrl_results, - expression + "(%s)" % calendar_mode) - data.CALENDAR.set_mode() - self.assertEqual(data.CALENDAR.mode, - data.Calendar.MODE_GREGORIAN) - - def test_timerecurrence(self): - """Test the recurring date/time series data model.""" - parser = parsers.TimeRecurrenceParser() - for expression, ctrl_results in get_timerecurrence_expansion_tests(): - try: - test_recurrence = parser.parse(expression) - except parsers.ISO8601SyntaxError: - raise ValueError( - "TimeRecurrenceParser test failed to parse '%s'" % - expression - ) - test_results = [] - for i, time_point in enumerate(test_recurrence): - if i > 2: - break - test_results.append(str(time_point)) - self.assertEqual(test_results, ctrl_results, expression) - if test_recurrence.start_point is None: - forward_method = test_recurrence.get_prev - backward_method = test_recurrence.get_next - else: - forward_method = test_recurrence.get_next - backward_method = test_recurrence.get_prev - test_points = [test_recurrence[0]] - test_points.append(forward_method(test_points[-1])) - test_points.append(forward_method(test_points[-1])) - test_results = [str(point) for point in test_points] - self.assertEqual(test_results, ctrl_results, expression) - if test_recurrence[2] is not None: - test_points = [test_recurrence[2]] - test_points.append(backward_method(test_points[-1])) - test_points.append(backward_method(test_points[-1])) - test_points.append(backward_method(test_points[-1])) - self.assertEqual(test_points[3], None, expression) - test_points.pop(3) - test_points.reverse() - test_results = [str(point) for point in test_points] - self.assertEqual(test_results, ctrl_results, expression) - - for expression, results in get_timerecurrence_membership_tests(): - try: - test_recurrence = parser.parse(expression) - except parsers.ISO8601SyntaxError: - raise ValueError( - "TimeRecurrenceParser test failed to parse '%s'" % - expression - ) - for timepoint_expression, ctrl_is_member in results: - timepoint = parsers.parse_timepoint_expression( - timepoint_expression) - test_is_member = test_recurrence.get_is_valid(timepoint) - self.assertEqual(test_is_member, ctrl_is_member, - timepoint_expression + " in " + expression) - - def test_timerecurrence_parser(self): - """Test the recurring date/time series parsing.""" - parser = parsers.TimeRecurrenceParser() - for expression, test_info in get_timerecurrenceparser_tests(): - try: - test_data = str(parser.parse(expression)) - except parsers.ISO8601SyntaxError: - raise ValueError("Parsing failed for %s" % expression) - ctrl_data = str(data.TimeRecurrence(**test_info)) - self.assertEqual(test_data, ctrl_data, expression) - - # data provider for the test test_get_local_time_zone_no_dst - # the format for the parameters is - # [tz_seconds, expected_hours, expected_minutes]] - get_local_time_zone_no_dst = [ - [45900, 12, 45], # pacific/chatham, +12:45 - [20700, 5, 45], # asia/kathmandu, +05:45 - [3600, 1, 0], # arctic/longyearbyen, +01:00 - [0, 0, 0], # UTC - [-10800, -3, 0], # america/sao_paulo, -03:00 - [-12600, -3, 30] # america/st_johns, -03:30 - ] - - @patch('isodatetime.timezone.time') - def test_get_local_time_zone_no_dst(self, mock_time): - """Test that the hour/minute returned is correct. - - Parts of the time module are mocked so that we can specify scenarios - without daylight saving time.""" - for tz_seconds, expected_hours, expected_minutes in \ - self.get_local_time_zone_no_dst: - # for a pre-defined timezone - mock_time.timezone.__neg__.return_value = tz_seconds - # time without dst - mock_time.daylight = False - # and localtime also without dst - mock_localtime = Mock() - mock_time.localtime.return_value = mock_localtime - mock_localtime.tm_isdst = 0 - hours, minutes = timezone.get_local_time_zone() - self.assertEqual(expected_hours, hours) - self.assertEqual(expected_minutes, minutes) - - # data provider for the test test_get_local_time_zone_with_dst - # the format for the parameters is - # [tz_seconds, tz_alt_seconds, expected_hours, expected_minutes] - get_local_time_zone_with_dst = [ - [45900, 49500, 13, 45], # pacific/chatham, +12:45 and +13:45 - [43200, 46800, 13, 0], # pacific/auckland, +12:00 and +13:00 - [3600, 7200, 2, 0], # arctic/longyearbyen, +01:00 - [0, 0, 0, 0], # UTC - [-10800, -7200, -2, 0], # america/sao_paulo, -03:00 and -02:00, - [-12600, -9000, -2, 30] # america/st_johns, -03:30 and -02:30 - ] - - @patch('isodatetime.timezone.time') - def test_get_local_time_zone_with_dst(self, mock_time): - """Test that the hour/minute returned is correct - - Parts of the time module are mocked so that we can specify scenarios - with daylight saving time.""" - for tz_seconds, tz_alt_seconds, expected_hours, expected_minutes in \ - self.get_local_time_zone_with_dst: - # for a pre-defined timezone - mock_time.timezone.__neg__.return_value = tz_seconds - # time without dst - mock_time.daylight = True - # and localtime also without dst - mock_localtime = MagicMock() - mock_time.localtime.return_value = mock_localtime - mock_localtime.tm_isdst = 1 - # and with the following alternative time for when dst is set - mock_time.altzone.__neg__.return_value = tz_alt_seconds - hours, minutes = timezone.get_local_time_zone() - self.assertEqual(expected_hours, hours) - self.assertEqual(expected_minutes, minutes) - - # data provider for the test test_get_local_time_zone_format - # the format for the parameters is - # [tz_seconds, tz_format_mode, expected_format] - get_test_get_local_time_zone_format = [ - # UTC - # UTC, returns Z, flags are never used for UTC - [0, timezone.TimeZoneFormatMode.normal, "Z"], - # Positive values, some with minutes != 0 - # asia/macao, +08:00 - [28800, timezone.TimeZoneFormatMode.normal, "+0800"], - # asia/macao, +08:00 - [28800, timezone.TimeZoneFormatMode.extended, "+08:00"], - # asia/macao, +08:00 - [28800, timezone.TimeZoneFormatMode.reduced, "+08"], - # pacific/chatham, +12:45 - [45900, timezone.TimeZoneFormatMode.normal, "+1245"], - # pacific/chatham, +12:45 - [45900, timezone.TimeZoneFormatMode.extended, "+12:45"], - # pacific/chatham, +12:45 - [45900, timezone.TimeZoneFormatMode.reduced, "+1245"], - # Negative values, some with minutes != 0 - # america/buenos_aires, -03:00 - [-10800, timezone.TimeZoneFormatMode.normal, "-0300"], - # america/buenos_aires, -03:00 - [-10800, timezone.TimeZoneFormatMode.extended, "-03:00"], - # america/buenos_aires, -03:00 - [-10800, timezone.TimeZoneFormatMode.reduced, "-03"], - # america/st_johns, -03:30 - [-12600, timezone.TimeZoneFormatMode.normal, "-0330"], - # america/st_johns, -03:30 - [-12600, timezone.TimeZoneFormatMode.extended, "-03:30"], - # america/st_johns, -03:30 - [-12600, timezone.TimeZoneFormatMode.reduced, "-0330"] - ] - - @patch('isodatetime.timezone.time') - def test_get_local_time_zone_format(self, mock_time): - """Test that the UTC offset string format is correct - - Parts of the time module are mocked so that we can specify scenarios - with certain timezone seconds offsets. DST is not really - important for this test case""" - for tz_seconds, tz_format_mode, expected_format in \ - self.get_test_get_local_time_zone_format: - # for a pre-defined timezone - mock_time.timezone.__neg__.return_value = tz_seconds - # time without dst - mock_time.daylight = False - # and localtime also without dst - mock_localtime = Mock() - mock_time.localtime.return_value = mock_localtime - mock_localtime.tm_isdst = 0 - tz_format = timezone.get_local_time_zone_format(tz_format_mode) - self.assertEqual(expected_format, tz_format) - - def test_duration_to_weeks(self): - """Test that the duration does not lose precision when converted - from days""" - duration_in_days = data.Duration(days=365) - duration_in_days.to_weeks() - duration_in_weeks = data.Duration(weeks=52) - self.assertEqual(duration_in_days.weeks, duration_in_weeks.weeks) - - def test_duration_floordiv(self): - """Test the existing dunder floordir, which will be removed when we - move to Python 3""" - duration = data.Duration(years=4, months=4, days=4, hours=4, - minutes=4, seconds=4) - duration //= 2 - self.assertEqual(2, duration.years) - self.assertEqual(2, duration.months) - self.assertEqual(2, duration.days) - self.assertEqual(2, duration.hours) - self.assertEqual(2, duration.minutes) - self.assertEqual(2, duration.seconds) - - def test_duration_in_weeks_floordiv(self): - """Test the existing dunder floordir, which will be removed when we - move to Python 3""" - duration = data.Duration(weeks=4) - duration //= 2 - self.assertEqual(2, duration.weeks) - - def test_timepoint_add_duration(self): - """Test adding a duration to a timepoint""" - seconds_added = 5 - timepoint = data.TimePoint(year=1900, month_of_year=1, day_of_month=1, - hour_of_day=1, minute_of_hour=1) - duration = data.Duration(seconds=seconds_added) - t = timepoint + duration - self.assertEqual(seconds_added, t.second_of_minute) - - def test_timepoint_add_duration_without_minute(self): - """Test adding a duration to a timepoint""" - seconds_added = 5 - timepoint = data.TimePoint(year=1900, month_of_year=1, day_of_month=1, - hour_of_day=1) - duration = data.Duration(seconds=seconds_added) - t = timepoint + duration - self.assertEqual(seconds_added, t.second_of_minute) - - def test_timepoint_dump_format(self): - """Test the timepoint format dump when values are programmatically - set to None""" - t = data.TimePoint(year="1984") - # commenting out month_of_year here is enough to make the test pass - t.month_of_year = None - t.day_of_year = None - t.week_of_year = None - with self.assertRaises(RuntimeError): - self.assertEqual("1984-01-01T00:00:00Z", str(t)) - - -if __name__ == '__main__': - unittest.main() diff --git a/lib/python/isodatetime/tests/test_datetimeoper.py b/lib/python/isodatetime/tests/test_datetimeoper.py deleted file mode 100644 index f7f39a0a81..0000000000 --- a/lib/python/isodatetime/tests/test_datetimeoper.py +++ /dev/null @@ -1,417 +0,0 @@ -# -*- coding: utf-8 -*- -# ---------------------------------------------------------------------------- -# Copyright (C) 2013-2019 British Crown (Met Office) & Contributors. -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program. If not, see . -# ---------------------------------------------------------------------------- -"""Test isodatetime.datetimeoper functionalities.""" - - -import os -import unittest -from unittest.mock import patch - - -from isodatetime.data import ( - get_timepoint_from_seconds_since_unix_epoch as seconds2point) -import isodatetime.datetimeoper - - -class TestDateTimeOperator(unittest.TestCase): - """Test isodatetime.datetimeoper.TestDateTimeOperator functionalities.""" - - @patch('isodatetime.datetimeoper.get_timepoint_for_now') - def test_process_time_point_str_now_0(self, mock_now_func): - """DateTimeOperator.process_time_point_str()""" - # 2009-02-13T23:31:30Z - mock_now = seconds2point(1234567890) - mock_now_func.return_value = mock_now - datetimeoper = isodatetime.datetimeoper.DateTimeOperator() - self.assertEqual(str(mock_now), datetimeoper.process_time_point_str()) - self.assertEqual( - str(mock_now), - datetimeoper.process_time_point_str(datetimeoper.STR_NOW)) - - @patch('isodatetime.datetimeoper.get_timepoint_for_now') - def test_process_time_point_str_ref_0(self, mock_now_func): - """DateTimeOperator.process_time_point_str('ref') - - But without explicit reference time, so default to now. - """ - # 2009-02-13T23:31:30Z - mock_now = seconds2point(1234567890) - mock_now_func.return_value = mock_now - datetimeoper = isodatetime.datetimeoper.DateTimeOperator() - # Ensure that the ISODATETIMEREF environment variable is not set - # Or the test may not work. - environ = os.environ.copy() - if datetimeoper.ENV_REF in environ: - del environ[datetimeoper.ENV_REF] - with patch.dict(os.environ, environ, clear=True): - self.assertEqual( - str(mock_now), - datetimeoper.process_time_point_str(datetimeoper.STR_REF)) - - def test_process_time_point_str_ref_1(self): - """DateTimeOperator.process_time_point_str('ref') - - With explicit reference time. - """ - # 2009-02-13T23:31:30Z - ref_point_str = str(seconds2point(1234567890)) - datetimeoper = isodatetime.datetimeoper.DateTimeOperator( - ref_point_str=ref_point_str) - self.assertEqual( - ref_point_str, - datetimeoper.process_time_point_str(datetimeoper.STR_REF)) - - def test_process_time_point_str_ref_2(self): - """DateTimeOperator.process_time_point_str('ref') - - With explicit reference time as ISODATETIMEREF environment variable. - """ - # 2009-02-13T23:31:30Z - ref_point_str = str(seconds2point(1234567890)) - # Set ISODATETIMEREF. - # Or the test may not work. - environ = os.environ.copy() - environ[isodatetime.datetimeoper.DateTimeOperator.ENV_REF] = ( - ref_point_str) - with patch.dict(os.environ, environ): - datetimeoper = isodatetime.datetimeoper.DateTimeOperator() - self.assertEqual( - ref_point_str, - datetimeoper.process_time_point_str(datetimeoper.STR_REF)) - - def test_process_time_point_str_x(self): - """DateTimeOperator.process_time_point_str(...) - - Basic parse and dump of a time point string. - """ - # 2009-02-13T23:31:30Z - point_str = str(seconds2point(1234567890)) - datetimeoper = isodatetime.datetimeoper.DateTimeOperator() - # Unix time - self.assertEqual( - '2019-01-11T10:40:15Z', - datetimeoper.process_time_point_str( - 'Fri 11 Jan 10:40:15 UTC 2019', - print_format=datetimeoper.CURRENT_TIME_DUMP_FORMAT_Z)) - # Basic - self.assertEqual( - point_str, - datetimeoper.process_time_point_str(point_str)) - # +ve offset - point_str_1 = str(seconds2point(1234567890 + 3600)) - self.assertEqual( - point_str_1, - datetimeoper.process_time_point_str(point_str, ['PT1H'])) - # +ve offset, time point like duration - point_str_1 = str(seconds2point(1234567890 + 3600)) - self.assertEqual( - point_str_1, - datetimeoper.process_time_point_str(point_str, ['P0000-00-00T01'])) - # -ve offset - point_str_2 = str(seconds2point(1234567890 - 86400)) - self.assertEqual( - point_str_2, - datetimeoper.process_time_point_str(point_str, ['-P1D'])) - # offsets that cancel out - self.assertEqual( - point_str, - datetimeoper.process_time_point_str(point_str, ['PT1H', '-PT60M'])) - # Multiple offsets in 1 string - point_str_3 = str(seconds2point(1234567890 - 86400 - 3600)) - self.assertEqual( - point_str_3, - datetimeoper.process_time_point_str(point_str, ['-P1DT1H'])) - # Multiple offsets - self.assertEqual( - point_str_3, - datetimeoper.process_time_point_str(point_str, ['-P1D', '-PT1H'])) - # Bad time point string - self.assertRaises( - ValueError, - datetimeoper.process_time_point_str, 'teatime') - # Bad offset string - with self.assertRaises( - isodatetime.datetimeoper.OffsetValueError, - ) as ctxmgr: - datetimeoper.process_time_point_str(point_str, ['ages']) - self.assertEqual('ages: bad offset value', str(ctxmgr.exception)) - # Bad offset string, unsupported time point like duration - with self.assertRaises( - isodatetime.datetimeoper.OffsetValueError, - ) as ctxmgr: - datetimeoper.process_time_point_str(point_str, ['P0000-W01-1']) - self.assertEqual( - 'P0000-W01-1: bad offset value', - str(ctxmgr.exception)) - - def test_process_time_point_str_calendar(self): - """DateTimeOperator.process_time_point_str(...) - - Alternate calendars. - """ - self.assertEqual( - 'gregorian', - isodatetime.datetimeoper.DateTimeOperator.get_calendar_mode()) - self.assertRaises( - KeyError, - isodatetime.datetimeoper.DateTimeOperator.set_calendar_mode, - 'milkywaygalactic') - for cal, str_in, offsets, str_out in [ - # 360day - ('360day', '20130301', ['-P1D'], '20130230'), - ('360day', '20130230', ['P1D'], '20130301'), - # 365day - ('365day', '20130301', ['-P1D'], '20130228'), - ('365day', '20130228', ['P1D'], '20130301'), - # 366day - ('366day', '20130301', ['-P1D'], '20130229'), - ('366day', '20130229', ['P1D'], '20130301'), - ]: - # Calendar mode, is unfortunately, a global variable, - # so needs to reset value on return. - calendar_mode = ( - isodatetime.datetimeoper.DateTimeOperator.get_calendar_mode()) - # Calendar mode by constructor. - try: - datetimeoper = isodatetime.datetimeoper.DateTimeOperator( - calendar_mode=cal) - self.assertEqual( - str_out, - datetimeoper.process_time_point_str(str_in, offsets)) - finally: - isodatetime.datetimeoper.DateTimeOperator.set_calendar_mode( - calendar_mode) - # Calendar mode by environment variable - try: - environ = os.environ.copy() - key = ( - isodatetime.datetimeoper.DateTimeOperator.ENV_CALENDAR_MODE - ) - environ[key] = cal - with patch.dict(os.environ, environ, clear=True): - datetimeoper = isodatetime.datetimeoper.DateTimeOperator() - self.assertEqual( - str_out, - datetimeoper.process_time_point_str( - str_in, offsets)) - finally: - isodatetime.datetimeoper.DateTimeOperator.set_calendar_mode( - calendar_mode) - - def test_process_time_point_str_format(self): - """DateTimeOperator.process_time_point_str(...) - - With parse_format and print_format. - """ - for parse_format, print_format, point_str_in, point_str_out in [ - ('%d/%m/%Y %H:%M:%S', '%Y-%m-%dT%H:%M:%S', - '24/12/2012 06:00:00', '2012-12-24T06:00:00'), - ('%Y,%M,%d,%H', '%Y%M%d%H', '2014,01,02,05', '2014010205'), - ('%Y%m%d', '%y%m%d', '20141231', '141231'), - ('%Y%m%d%H%M%S', '%s', '20140402100000', '1396432800'), - ('%s', '%Y%m%dT%H%M%S%z', '1396429200', '20140402T090000+0000'), - ('%d/%m/%Y %H:%M:%S', 'CCYY-MM-DDThh:mm', - '24/12/2012 06:00:00', '2012-12-24T06:00'), - (None, 'CCYY-MM-DDThh:mm+01:00', - '2014-091T15:14:03Z', '2014-04-01T16:14+01:00'), - (None, '%m', '2014-02-01T04:05:06', '02'), - (None, '%Y', '2014-02-01T04:05:06', '2014'), - (None, '%H', '2014-02-01T04:05:06', '04'), - (None, '%Y%m%d_%H%M%S', '2014-02-01T04:05:06', '20140201_040506'), - (None, '%Y.file', '2014-02-01T04:05:06', '2014.file'), - (None, 'y%Ym%md%d', '2014-02-01T04:05:06', 'y2014m02d01'), - (None, '%F', '2014-02-01T04:05:06', '2014-02-01'), - - ]: - datetimeoper = isodatetime.datetimeoper.DateTimeOperator( - utc_mode=True, - parse_format=parse_format) - self.assertEqual( - point_str_out, - datetimeoper.process_time_point_str( - point_str_in, print_format=print_format)) - # Bad parse format - datetimeoper = isodatetime.datetimeoper.DateTimeOperator( - parse_format='%o') - with self.assertRaises(ValueError) as ctxmgr: - datetimeoper.process_time_point_str('0000') - self.assertEqual( - "'o' is a bad directive in format '%o'", - str(ctxmgr.exception)) - - def test_format_duration_str_x(self): - """DateTimeOperator.format_duration_str(...)""" - datetimeoper = isodatetime.datetimeoper.DateTimeOperator() - # Good ones - for print_format, duration_str_in, duration_out in [ - ('s', 'PT1M', 60.0), - ('s', 'P1DT1H1M1S', 90061.0), - ('m', 'PT1S', 0.0166666666667), - ('h', 'P832DT23H12M45S', 19991.2125), - ('S', '-PT1M1S', -61.0), - ]: - self.assertAlmostEqual( - duration_out, - datetimeoper.format_duration_str( - duration_str_in, print_format)) - # Bad ones - for print_format, duration_str_in in [ - ('y', 'PT1M'), - ('s', 'quickquick'), - ]: - self.assertRaises( - ValueError, - datetimeoper.format_duration_str, - duration_str_in, print_format) - - def test_diff_time_point_strs(self): - """DateTimeOperator.diff_time_point_strs(...)""" - datetimeoper = isodatetime.datetimeoper.DateTimeOperator( - ref_point_str='20150106') - for ( - time_point_str1, - time_point_str2, - offsets1, offsets2, - print_format, - duration_print_format, - duration_out, - ) in [ - ( # Positive - '20130101T12', - '20130301', - None, - None, - None, - None, - 'P58DT12H', - ), - ( # Positive, non integer seconds - # Use (3.1 - 3.0) to bypass str(float) precision issue - '20190101T010203', - '20190101T010203.1', - None, - None, - None, - None, - 'PT%sS' % (str(3.1 - 3.0).replace('.', ',')), - ), - ( # Positive, non integer seconds, print format - # Use (3.1 - 3.0) to bypass str(float) precision issue - '20190101T010203', - '20190101T010203.1', - None, - None, - 's', - None, - str(3.1 - 3.0), - ), - ( # Offset 1, reference time 2, positive - '20140101', - 'ref', - ['P11M24D'], - None, - None, - None, - 'P12D', - ), - ( # Offset 2, positive - '20100101T00', - '20100201T00', - None, - ['P1D'], - None, - None, - 'P32D', - ), - ( # Neutral - '20151225T00', - '20151225', - None, - None, - None, - None, - 'P0Y', - ), - ( # Negative - '20150101T12', - '20130301', - None, - None, - None, - None, - '-P671DT12H', - ), - ( # Alternate format - '20130101T12', - '20130301', - None, - None, - 'y,m,d,h,M,s', - None, - '0,0,58,12,0,0', - ), - ( # Offset 2, alternate format - '0000', - '0000', - ['-PT2M'], - None, - 'y,m,d,h,M,s', - None, - '0,0,0,0,2,0', - ), - ( # As seconds, positive - '2000-01-01T00:00:00', - '2000-01-01T01:00:00', - None, - None, - None, - 's', - 3600.0, - ), - ( # As seconds, neutral - '2000-01-01T00:00:00', - '2000-01-01T00:00:00', - None, - None, - None, - 's', - 0.0, - ), - ( # As seconds, negative - '2000-01-01T00:00:00', - '1999-12-31T23:00:00', - None, - None, - None, - 's', - -3600.0, - ), - ]: - self.assertEqual( - duration_out, - datetimeoper.diff_time_point_strs( - time_point_str1, - time_point_str2, - offsets1, - offsets2, - print_format, - duration_print_format)) - - -if __name__ == '__main__': - unittest.main() diff --git a/lib/python/isodatetime/tests/test_main.py b/lib/python/isodatetime/tests/test_main.py deleted file mode 100644 index d365ea62ca..0000000000 --- a/lib/python/isodatetime/tests/test_main.py +++ /dev/null @@ -1,253 +0,0 @@ -# -*- coding: utf-8 -*- -# ---------------------------------------------------------------------------- -# Copyright (C) 2013-2019 British Crown (Met Office) & Contributors. -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program. If not, see . -# ---------------------------------------------------------------------------- -"""Test isodatetime.main.""" - - -import os -import sys -import unittest -from unittest.mock import patch - - -import isodatetime -import isodatetime.main - - -class TestMain(unittest.TestCase): - """Test isodatetime.main.main.""" - - @patch('builtins.print') - def test_1_version(self, mock_print): - """Test print version.""" - argv = sys.argv - for args in [['--version'], ['-V']]: - sys.argv = [''] + args - try: - isodatetime.main.main() - mock_print.assert_called_with(isodatetime.__version__) - finally: - sys.argv = argv - - @patch('builtins.print') - def test_1_null(self, mock_print): - """Test calling usage 1, no or now argument.""" - argv = sys.argv - with patch.object( - isodatetime.main.DateTimeOperator, - 'process_time_point_str', - return_value='20190101T1234Z' - ): - for args in [[''], ['now']]: - sys.argv = args - try: - isodatetime.main.main() - mock_print.assert_called_with('20190101T1234Z') - finally: - sys.argv = argv - - @patch('builtins.print') - def test_1_good(self, mock_print): - """Test calling usage 1, sample good arguments.""" - env_ref = isodatetime.main.DateTimeOperator.ENV_REF - ref = os.environ.get(env_ref) - argv = sys.argv - for args, out in [ - (['20200101T00Z'], '20200101T00Z'), - (['ref'], '20201225T0000Z'), - # UTC mode - (['-u', '20200101T00+0100'], '20191231T23+0000'), - (['--utc', '20200101T00+0100'], '20191231T23+0000'), - # With offsets - (['-s', 'P1D', '20191231T00Z'], '20200101T00Z'), - (['-s', 'P1D', '--offset=PT1H', '20191231T00Z'], '20200101T01Z'), - # Print format - (['-f', 'CCYY', '20191231T00Z'], '2019'), - (['--format', 'CCYY', '20191231T00Z'], '2019'), - (['--print-format', 'CCYY', '20191231T00Z'], '2019'), - (['20200101T00-1130', '-f', 'CCYYMMDDThhmm+0000'], - '20200101T1130+0000'), - # Parse format - (['--parse-format=%d/%m/%Y', '-f', 'CCYY-MM-DD', '31/12/2019'], - '2019-12-31'), - (['-p', '%d/%m/%Y', '-f', 'CCYY-MM-DD', '31/12/2019'], - '2019-12-31'), - ]: - sys.argv = [''] + args - os.environ[env_ref] = '20201225T0000Z' - try: - isodatetime.main.main() - mock_print.assert_called_with(out) - finally: - sys.argv = argv - if ref is not None: - os.environ[env_ref] = ref - else: - del os.environ[env_ref] - - @patch('builtins.print') - def test_1_bad(self, mock_print): - """Test calling usage 1, sample bad arguments.""" - argv = sys.argv - for args, out in [ - # Bad time point string - (['201abc'], 'Invalid ISO 8601 date representation: 201abc'), - # Bad offset string - (['-s', 'add-a-year', '2019'], 'add-a-year: bad offset value'), - ]: - mock_print.reset_mock() - sys.argv = [''] + args - try: - with self.assertRaises(SystemExit) as ctxmgr: - isodatetime.main.main() - mock_print.assert_not_called() - self.assertEqual(out, str(ctxmgr.exception)) - finally: - sys.argv = argv - - @patch('builtins.print') - def test_2_good(self, mock_print): - """Test calling usage 2, sample good arguments.""" - env_ref = isodatetime.main.DateTimeOperator.ENV_REF - ref = os.environ.get(env_ref) - argv = sys.argv - for args, out in [ - # Same - (['ref', 'ref'], 'P0Y'), - (['20380119', '20380119'], 'P0Y'), - # Positive duration - (['20181225', '20191225'], 'P365D'), - (['-1', 'PT12H', '-2', 'PT12H', '20181225', '20191225'], 'P365D'), - # Negative duration - (['20191225', '20181225'], '-P365D'), - (['--offset1=-PT6H', '--offset2=-PT6H', '20191225', '20181225'], - '-P365D'), - ]: - sys.argv = [''] + args - os.environ[env_ref] = '20201225T0000Z' - try: - isodatetime.main.main() - mock_print.assert_called_with(out) - finally: - sys.argv = argv - if ref is not None: - os.environ[env_ref] = ref - else: - del os.environ[env_ref] - - @patch('builtins.print') - def test_2_bad(self, mock_print): - """Test calling usage 2, sample bad arguments.""" - argv = sys.argv - for args, out in [ - # Bad time point string - (['201abc', '2020'], - 'Invalid ISO 8601 date representation: 201abc'), - # Bad offset string - (['-1', 'add-a-year', '2018', '2019'], - 'add-a-year: bad offset value'), - ]: - mock_print.reset_mock() - sys.argv = [''] + args - try: - with self.assertRaises(SystemExit) as ctxmgr: - isodatetime.main.main() - mock_print.assert_not_called() - self.assertEqual(out, str(ctxmgr.exception)) - finally: - sys.argv = argv - - @patch('builtins.print') - def test_3_good(self, mock_print): - """Test calling usage 3, sample good arguments.""" - argv = sys.argv - for args, out in [ - # Same - (['--as-total=s', 'PT1H30M'], 5400), - (['--as-total=s', 'P1D'], 86400), - (['--as-total=h', 'P1D'], 24), - ]: - sys.argv = [''] + args - try: - isodatetime.main.main() - mock_print.assert_called_with(out) - finally: - sys.argv = argv - - @patch('builtins.print') - def test_3_bad(self, mock_print): - """Test calling usage 3, sample bad arguments.""" - argv = sys.argv - mock_print.reset_mock() - sys.argv = ['', '--as-total=s', 'PS4'] - try: - with self.assertRaises(SystemExit) as ctxmgr: - isodatetime.main.main() - mock_print.assert_not_called() - self.assertEqual( - 'Invalid ISO 8601 duration representation: PS4', - str(ctxmgr.exception)) - finally: - sys.argv = argv - - @patch('builtins.print') - def test_4_good(self, mock_print): - """Test calling usage 4, sample good arguments.""" - argv = sys.argv - for args, out in [ - # Forward - (['-u', 'R/2020/P1Y'], - '\n'.join('%d-01-01T00:00:00Z' % i for i in range(2020, 2030))), - (['-u', 'R3/2020/P1Y'], - '\n'.join('%d-01-01T00:00:00Z' % i for i in range(2020, 2023))), - (['-u', '--max=5', 'R/2020/P1Y'], - '\n'.join('%d-01-01T00:00:00Z' % i for i in range(2020, 2025))), - (['-u', '--max=15', 'R/2020/P1Y'], - '\n'.join('%d-01-01T00:00:00Z' % i for i in range(2020, 2035))), - (['--print-format=%Y', 'R/2020/P1Y'], - '\n'.join('%d' % i for i in range(2020, 2030))), - # Reverse - (['-u', 'R/P1Y/2020'], - '\n'.join('%d-01-01T00:00:00Z' % i - for i in range(2020, 2010, -1))), - ]: - sys.argv = [''] + args - try: - isodatetime.main.main() - mock_print.assert_called_with(out) - finally: - sys.argv = argv - - @patch('builtins.print') - def test_4_bad(self, mock_print): - """Test calling usage 4, sample bad arguments.""" - argv = sys.argv - mock_print.reset_mock() - sys.argv = ['', 'R/2020/2025'] - try: - with self.assertRaises(SystemExit) as ctxmgr: - isodatetime.main.main() - mock_print.assert_not_called() - self.assertEqual( - 'Invalid ISO 8601 recurrence representation: R/2020/2025', - str(ctxmgr.exception)) - finally: - sys.argv = argv - - -if __name__ == '__main__': - unittest.main() diff --git a/lib/python/isodatetime/tests/test_time_point.py b/lib/python/isodatetime/tests/test_time_point.py deleted file mode 100644 index b46d7ecb1e..0000000000 --- a/lib/python/isodatetime/tests/test_time_point.py +++ /dev/null @@ -1,151 +0,0 @@ -# -*- coding: utf-8 -*- -# ---------------------------------------------------------------------------- -# Copyright (C) 2013-2019 British Crown (Met Office) & Contributors. -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program. If not, see . -# ---------------------------------------------------------------------------- - -"""This tests the ISO 8601 parsing and data model functionality.""" - -import datetime -import pytest -import random -import unittest -import concurrent.futures - -from isodatetime.data import TimePoint, Duration, get_days_since_1_ad - - -def daterange(start_date, end_date): - """https://stackoverflow.com/a/1060330""" - for n in range(1 + int((end_date - start_date).days)): - yield start_date + datetime.timedelta(n) - - -test_duration_attributes = [ - ("weeks", 110), - ("days", 770), - ("hours", 770 * 24), - ("minutes", 770 * 24 * 60), - ("seconds", 770 * 24 * 60 * 60)] - - -@pytest.mark.slow -class TestTimePointCompat(unittest.TestCase): - """Test time point compatibility with "datetime".""" - - def test_timepoint(self): - """Test the time point data model (takes a while). - - For a range of years (e.g. 1801 to 2403) it iterates through each - year, then creates another range with the days in this year. Finally - performs a series of tests, failing if any operation results in - an error.""" - - for test_year in range(1801, 2403): - my_date = datetime.datetime(test_year, 1, 1) - stop_date = datetime.datetime(test_year + 1, 1, 1) - - # test each day in the year concurrently - # using the number of cores in a travis ci server for max_workers - with concurrent.futures.ThreadPoolExecutor(max_workers=2)\ - as executor: - futures = {executor.submit(self._do_test_dates, d): - d for d in daterange(my_date, stop_date)} - concurrent.futures.wait(futures) - - # Each day takes approx 0.5s to compute, so let's give - # it four times the normal as buffer - for _, future in enumerate( - concurrent.futures.as_completed(futures, timeout=2.0)): - future.result() # This will also raise any exceptions - - def _do_test_dates(self, my_date): - """Performs a series of tests against a given date. - - This method does some time consuming operations, which are not IO - bound, so this method is a good candidate to be run concurrently. - - :param my_date: a date to be tested - :type my_date: datetime.datetime - """ - ctrl_data = my_date.isocalendar() - test_date = TimePoint( - year=my_date.year, - month_of_year=my_date.month, - day_of_month=my_date.day - ) - test_week_date = test_date.to_week_date() - test_data = test_week_date.get_week_date() - self.assertEqual(test_data, ctrl_data) - ctrl_data = (my_date.year, my_date.month, my_date.day) - test_data = test_week_date.get_calendar_date() - self.assertEqual(test_data, ctrl_data) - ctrl_data = my_date.toordinal() - year, day_of_year = test_date.get_ordinal_date() - test_data = day_of_year - test_data += get_days_since_1_ad(year - 1) - self.assertEqual(test_data, ctrl_data) - for attribute, attr_max in test_duration_attributes: - kwargs = {attribute: random.randrange(0, attr_max)} - ctrl_data = my_date + datetime.timedelta(**kwargs) - ctrl_data = ctrl_data.year, ctrl_data.month, ctrl_data.day - test_data = ( - (test_date + Duration(**kwargs)).get_calendar_date()) - self.assertEqual(test_data, ctrl_data) - ctrl_data = my_date - datetime.timedelta(**kwargs) - ctrl_data = ctrl_data.year, ctrl_data.month, ctrl_data.day - # TBD: the subtraction is quite slow. Much slower than other - # operations. Could be related to the fact it converts the value - # in kwargs to negative multiplying by -1 (i.e. from __sub__ to - # __mul__), and also adds it to the date (i.e. __add__). - # Profiling the tests, the __sub__ operation used in the next - # line will appear amongst the top of time consuming operations. - test_data = ( - (test_date - Duration(**kwargs)).get_calendar_date()) - self.assertEqual(test_data, ctrl_data) - kwargs = {} - for attribute, attr_max in test_duration_attributes: - kwargs[attribute] = random.randrange(0, attr_max) - test_date_minus = test_date - Duration(**kwargs) - test_data = test_date - test_date_minus - ctrl_data = Duration(**kwargs) - self.assertEqual(test_data, ctrl_data) - test_data = test_date_minus + (test_date - test_date_minus) - ctrl_data = test_date - self.assertEqual(test_data, ctrl_data) - test_data = (test_date_minus + Duration(**kwargs)) - ctrl_data = test_date - self.assertEqual(test_data, ctrl_data) - ctrl_data = ( - my_date + - datetime.timedelta(minutes=450) + - datetime.timedelta(hours=5) - - datetime.timedelta(seconds=500, weeks=5)) - ctrl_data = [ - (ctrl_data.year, ctrl_data.month, ctrl_data.day), - (ctrl_data.hour, ctrl_data.minute, ctrl_data.second)] - test_data = ( - test_date + Duration(minutes=450) + - Duration(hours=5) - - Duration(weeks=5, seconds=500) - ) - test_data = [ - test_data.get_calendar_date(), - test_data.get_hour_minute_second()] - self.assertEqual(test_data, ctrl_data) - - -if __name__ == '__main__': - unittest.main() diff --git a/lib/python/isodatetime/timezone.py b/lib/python/isodatetime/timezone.py deleted file mode 100644 index 05ad5444ae..0000000000 --- a/lib/python/isodatetime/timezone.py +++ /dev/null @@ -1,59 +0,0 @@ -# -*- coding: utf-8 -*- -# ---------------------------------------------------------------------------- -# Copyright (C) 2013-2019 British Crown (Met Office) & Contributors. -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program. If not, see . -# ---------------------------------------------------------------------------- - -"""This provides utilities for extracting the local time zone.""" - -import time -import math - - -class TimeZoneFormatMode(object): - normal = "normal" - reduced = "reduced" - extended = "extended" - - -def get_local_time_zone(): - """Return the current local UTC offset in hours and minutes.""" - utc_offset_seconds = -time.timezone - if time.localtime().tm_isdst == 1 and time.daylight: - utc_offset_seconds = -time.altzone - utc_offset_minutes = (utc_offset_seconds // 60) % 60 - utc_offset_hours = math.floor(utc_offset_seconds / float(3600)) if \ - utc_offset_seconds > 0 else math.ceil(utc_offset_seconds / float(3600)) - return int(utc_offset_hours), utc_offset_minutes - - -def get_local_time_zone_format(tz_fmt_mode=TimeZoneFormatMode.normal): - """Return a string denoting the current local UTC offset. - - :param tz_fmt_mode: - :type tz_fmt_mode: TimeZoneFormat: - """ - utc_offset_hours, utc_offset_minutes = get_local_time_zone() - if utc_offset_hours == 0 and utc_offset_minutes == 0: - return "Z" - reduced_timezone_template = "%s%02d" - timezone_template = "%s%02d%02d" - if tz_fmt_mode == TimeZoneFormatMode.extended: - timezone_template = "%s%02d:%02d" - sign = "-" if (utc_offset_hours < 0 or utc_offset_minutes < 0) else "+" - if tz_fmt_mode == TimeZoneFormatMode.reduced and utc_offset_minutes == 0: - return reduced_timezone_template % (sign, abs(utc_offset_hours)) - return timezone_template % ( - sign, abs(utc_offset_hours), abs(utc_offset_minutes)) diff --git a/lib/python/rose/tests/test_ancils/unicode.txt b/lib/python/rose/tests/test_ancils/unicode.txt deleted file mode 100644 index b68266c1f5..0000000000 --- a/lib/python/rose/tests/test_ancils/unicode.txt +++ /dev/null @@ -1,301 +0,0 @@ -UTF-8 decoder capability and stress test ----------------------------------------- - -Markus Kuhn - 2015-08-28 - CC BY 4.0 - -This test file can help you examine, how your UTF-8 decoder handles -various types of correct, malformed, or otherwise interesting UTF-8 -sequences. This file is not meant to be a conformance test. It does -not prescribe any particular outcome. Therefore, there is no way to -"pass" or "fail" this test file, even though the text does suggest a -preferable decoder behaviour at some places. Its aim is, instead, to -help you think about, and test, the behaviour of your UTF-8 decoder on a -systematic collection of unusual inputs. Experience so far suggests -that most first-time authors of UTF-8 decoders find at least one -serious problem in their decoder using this file. - -The test lines below cover boundary conditions, malformed UTF-8 -sequences, as well as correctly encoded UTF-8 sequences of Unicode code -points that should never occur in a correct UTF-8 file. - -According to ISO 10646-1:2000, sections D.7 and 2.3c, a device -receiving UTF-8 shall interpret a "malformed sequence in the same way -that it interprets a character that is outside the adopted subset" and -"characters that are not within the adopted subset shall be indicated -to the user" by a receiving device. One commonly used approach in -UTF-8 decoders is to replace any malformed UTF-8 sequence by a -replacement character (U+FFFD), which looks a bit like an inverted -question mark, or a similar symbol. It might be a good idea to -visually distinguish a malformed UTF-8 sequence from a correctly -encoded Unicode character that is just not available in the current -font but otherwise fully legal, even though ISO 10646-1 doesn't -mandate this. In any case, just ignoring malformed sequences or -unavailable characters does not conform to ISO 10646, will make -debugging more difficult, and can lead to user confusion. - -Please check, whether a malformed UTF-8 sequence is (1) represented at -all, (2) represented by exactly one single replacement character (or -equivalent signal), and (3) the following quotation mark after an -illegal UTF-8 sequence is correctly displayed, i.e. proper -resynchronization takes place immediately after any malformed -sequence. This file says "THE END" in the last line, so if you don't -see that, your decoder crashed somehow before, which should always be -cause for concern. - -All lines in this file are exactly 79 characters long (plus the line -feed). In addition, all lines end with "|", except for the two test -lines 2.1.1 and 2.2.1, which contain non-printable ASCII controls -U+0000 and U+007F. If you display this file with a fixed-width font, -these "|" characters should all line up in column 79 (right margin). -This allows you to test quickly, whether your UTF-8 decoder finds the -correct number of characters in every line, that is whether each -malformed sequences is replaced by a single replacement character. - -Note that, as an alternative to the notion of malformed sequence used -here, it is also a perfectly acceptable (and in some situations even -preferable) solution to represent each individual byte of a malformed -sequence with a replacement character. If you follow this strategy in -your decoder, then please ignore the "|" column. - - -Here come the tests: | - | -1 Some correct UTF-8 text | - | -You should see the Greek word 'kosme': "κόσμε" | - | -2 Boundary condition test cases | - | -2.1 First possible sequence of a certain length | - | -2.1.1 1 byte (U-00000000): "�" -2.1.2 2 bytes (U-00000080): "€" | -2.1.3 3 bytes (U-00000800): "à €" | -2.1.4 4 bytes (U-00010000): "ð€€" | -2.1.5 5 bytes (U-00200000): "�����" | -2.1.6 6 bytes (U-04000000): "������" | - | -2.2 Last possible sequence of a certain length | - | -2.2.1 1 byte (U-0000007F): "" -2.2.2 2 bytes (U-000007FF): "ß¿" | -2.2.3 3 bytes (U-0000FFFF): "ï¿¿" | -2.2.4 4 bytes (U-001FFFFF): "����" | -2.2.5 5 bytes (U-03FFFFFF): "�����" | -2.2.6 6 bytes (U-7FFFFFFF): "������" | - | -2.3 Other boundary conditions | - | -2.3.1 U-0000D7FF = ed 9f bf = "퟿" | -2.3.2 U-0000E000 = ee 80 80 = "" | -2.3.3 U-0000FFFD = ef bf bd = "�" | -2.3.4 U-0010FFFF = f4 8f bf bf = "ô¿¿" | -2.3.5 U-00110000 = f4 90 80 80 = "����" | - | -3 Malformed sequences | - | -3.1 Unexpected continuation bytes | - | -Each unexpected continuation byte should be separately signalled as a | -malformed sequence of its own. | - | -3.1.1 First continuation byte 0x80: "�" | -3.1.2 Last continuation byte 0xbf: "�" | - | -3.1.3 2 continuation bytes: "��" | -3.1.4 3 continuation bytes: "���" | -3.1.5 4 continuation bytes: "����" | -3.1.6 5 continuation bytes: "�����" | -3.1.7 6 continuation bytes: "������" | -3.1.8 7 continuation bytes: "�������" | - | -3.1.9 Sequence of all 64 possible continuation bytes (0x80-0xbf): | - | - "���������������� | - ���������������� | - ���������������� | - ����������������" | - | -3.2 Lonely start characters | - | -3.2.1 All 32 first bytes of 2-byte sequences (0xc0-0xdf), | - each followed by a space character: | - | - "� � � � � � � � � � � � � � � � | - � � � � � � � � � � � � � � � � " | - | -3.2.2 All 16 first bytes of 3-byte sequences (0xe0-0xef), | - each followed by a space character: | - | - "� � � � � � � � � � � � � � � � " | - | -3.2.3 All 8 first bytes of 4-byte sequences (0xf0-0xf7), | - each followed by a space character: | - | - "� � � � � � � � " | - | -3.2.4 All 4 first bytes of 5-byte sequences (0xf8-0xfb), | - each followed by a space character: | - | - "� � � � " | - | -3.2.5 All 2 first bytes of 6-byte sequences (0xfc-0xfd), | - each followed by a space character: | - | - "� � " | - | -3.3 Sequences with last continuation byte missing | - | -All bytes of an incomplete sequence should be signalled as a single | -malformed sequence, i.e., you should see only a single replacement | -character in each of the next 10 tests. (Characters as in section 2) | - | -3.3.1 2-byte sequence with last byte missing (U+0000): "�" | -3.3.2 3-byte sequence with last byte missing (U+0000): "��" | -3.3.3 4-byte sequence with last byte missing (U+0000): "���" | -3.3.4 5-byte sequence with last byte missing (U+0000): "����" | -3.3.5 6-byte sequence with last byte missing (U+0000): "�����" | -3.3.6 2-byte sequence with last byte missing (U-000007FF): "�" | -3.3.7 3-byte sequence with last byte missing (U-0000FFFF): "�" | -3.3.8 4-byte sequence with last byte missing (U-001FFFFF): "���" | -3.3.9 5-byte sequence with last byte missing (U-03FFFFFF): "����" | -3.3.10 6-byte sequence with last byte missing (U-7FFFFFFF): "�����" | - | -3.4 Concatenation of incomplete sequences | - | -All the 10 sequences of 3.3 concatenated, you should see 10 malformed | -sequences being signalled: | - | - "�����������������������������" | - | -3.5 Impossible bytes | - | -The following two bytes cannot appear in a correct UTF-8 string | - | -3.5.1 fe = "�" | -3.5.2 ff = "�" | -3.5.3 fe fe ff ff = "����" | - | -4 Overlong sequences | - | -The following sequences are not malformed according to the letter of | -the Unicode 2.0 standard. However, they are longer then necessary and | -a correct UTF-8 encoder is not allowed to produce them. A "safe UTF-8 | -decoder" should reject them just like malformed sequences for two | -reasons: (1) It helps to debug applications if overlong sequences are | -not treated as valid representations of characters, because this helps | -to spot problems more quickly. (2) Overlong sequences provide | -alternative representations of characters, that could maliciously be | -used to bypass filters that check only for ASCII characters. For | -instance, a 2-byte encoded line feed (LF) would not be caught by a | -line counter that counts only 0x0a bytes, but it would still be | -processed as a line feed by an unsafe UTF-8 decoder later in the | -pipeline. From a security point of view, ASCII compatibility of UTF-8 | -sequences means also, that ASCII characters are *only* allowed to be | -represented by ASCII bytes in the range 0x00-0x7f. To ensure this | -aspect of ASCII compatibility, use only "safe UTF-8 decoders" that | -reject overlong UTF-8 sequences for which a shorter encoding exists. | - | -4.1 Examples of an overlong ASCII character | - | -With a safe UTF-8 decoder, all of the following five overlong | -representations of the ASCII character slash ("/") should be rejected | -like a malformed UTF-8 sequence, for instance by substituting it with | -a replacement character. If you see a slash below, you do not have a | -safe UTF-8 decoder! | - | -4.1.1 U+002F = c0 af = "��" | -4.1.2 U+002F = e0 80 af = "���" | -4.1.3 U+002F = f0 80 80 af = "����" | -4.1.4 U+002F = f8 80 80 80 af = "�����" | -4.1.5 U+002F = fc 80 80 80 80 af = "������" | - | -4.2 Maximum overlong sequences | - | -Below you see the highest Unicode value that is still resulting in an | -overlong sequence if represented with the given number of bytes. This | -is a boundary test for safe UTF-8 decoders. All five characters should | -be rejected like malformed UTF-8 sequences. | - | -4.2.1 U-0000007F = c1 bf = "��" | -4.2.2 U-000007FF = e0 9f bf = "���" | -4.2.3 U-0000FFFF = f0 8f bf bf = "����" | -4.2.4 U-001FFFFF = f8 87 bf bf bf = "�����" | -4.2.5 U-03FFFFFF = fc 83 bf bf bf bf = "������" | - | -4.3 Overlong representation of the NUL character | - | -The following five sequences should also be rejected like malformed | -UTF-8 sequences and should not be treated like the ASCII NUL | -character. | - | -4.3.1 U+0000 = c0 80 = "��" | -4.3.2 U+0000 = e0 80 80 = "���" | -4.3.3 U+0000 = f0 80 80 80 = "����" | -4.3.4 U+0000 = f8 80 80 80 80 = "�����" | -4.3.5 U+0000 = fc 80 80 80 80 80 = "������" | - | -5 Illegal code positions | - | -The following UTF-8 sequences should be rejected like malformed | -sequences, because they never represent valid ISO 10646 characters and | -a UTF-8 decoder that accepts them might introduce security problems | -comparable to overlong UTF-8 sequences. | - | -5.1 Single UTF-16 surrogates | - | -5.1.1 U+D800 = ed a0 80 = "���" | -5.1.2 U+DB7F = ed ad bf = "���" | -5.1.3 U+DB80 = ed ae 80 = "���" | -5.1.4 U+DBFF = ed af bf = "���" | -5.1.5 U+DC00 = ed b0 80 = "���" | -5.1.6 U+DF80 = ed be 80 = "���" | -5.1.7 U+DFFF = ed bf bf = "���" | - | -5.2 Paired UTF-16 surrogates | - | -5.2.1 U+D800 U+DC00 = ed a0 80 ed b0 80 = "������" | -5.2.2 U+D800 U+DFFF = ed a0 80 ed bf bf = "������" | -5.2.3 U+DB7F U+DC00 = ed ad bf ed b0 80 = "������" | -5.2.4 U+DB7F U+DFFF = ed ad bf ed bf bf = "������" | -5.2.5 U+DB80 U+DC00 = ed ae 80 ed b0 80 = "������" | -5.2.6 U+DB80 U+DFFF = ed ae 80 ed bf bf = "������" | -5.2.7 U+DBFF U+DC00 = ed af bf ed b0 80 = "������" | -5.2.8 U+DBFF U+DFFF = ed af bf ed bf bf = "������" | - | -5.3 Noncharacter code positions | - | -The following "noncharacters" are "reserved for internal use" by | -applications, and according to older versions of the Unicode Standard | -"should never be interchanged". Unicode Corrigendum #9 dropped the | -latter restriction. Nevertheless, their presence in incoming UTF-8 data | -can remain a potential security risk, depending on what use is made of | -these codes subsequently. Examples of such internal use: | - | - - Some file APIs with 16-bit characters may use the integer value -1 | - = U+FFFF to signal an end-of-file (EOF) or error condition. | - | - - In some UTF-16 receivers, code point U+FFFE might trigger a | - byte-swap operation (to convert between UTF-16LE and UTF-16BE). | - | -With such internal use of noncharacters, it may be desirable and safer | -to block those code points in UTF-8 decoders, as they should never | -occur legitimately in incoming UTF-8 data, and could trigger unsafe | -behaviour in subsequent processing. | - | -Particularly problematic noncharacters in 16-bit applications: | - | -5.3.1 U+FFFE = ef bf be = "￾" | -5.3.2 U+FFFF = ef bf bf = "ï¿¿" | - | -Other noncharacters: | - | -5.3.3 U+FDD0 .. U+FDEF = "ï·ï·‘﷒﷓﷔﷕﷖﷗﷘﷙﷚﷛﷜ï·ï·žï·Ÿï· ï·¡ï·¢ï·£ï·¤ï·¥ï·¦ï·§ï·¨ï·©ï·ªï·«ï·¬ï·­ï·®ï·¯"| - | -5.3.4 U+nFFFE U+nFFFF (for n = 1..10) | - | - "🿾🿿𯿾𯿿𿿾𿿿ñ¿¾ñ¿¿ñŸ¿¾ñŸ¿¿ñ¯¿¾ñ¯¿¿ñ¿¿¾ñ¿¿¿ò¿¾ò¿¿ | - òŸ¿¾òŸ¿¿ò¯¿¾ò¯¿¿ò¿¿¾ò¿¿¿ó¿¾ó¿¿óŸ¿¾óŸ¿¿ó¯¿¾ó¯¿¿ó¿¿¾ó¿¿¿ô¿¾ô¿¿" | - | -THE END | - diff --git a/lib/python/rose/tests/test_unicode_handling.py b/lib/python/rose/tests/test_unicode_handling.py deleted file mode 100644 index 67300cf6cb..0000000000 --- a/lib/python/rose/tests/test_unicode_handling.py +++ /dev/null @@ -1,42 +0,0 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- -# Copyright (C) 2012-2019 British Crown (Met Office) & Contributors. -# -# This file is part of Rose, a framework for meteorological suites. -# -# Rose is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Rose is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Rose. If not, see . -# ----------------------------------------------------------------------------- -# Designed to test how unusual and/or broken unicode (utf-8) is handled by rose -# reporter -import os -from rose.reporter import Reporter - - -def test_mojibake_basic(): - reporterinstance = Reporter() - msg = """ăѣð” Õ®á»Å¿Ä£ÈŸáŽ¥ð’‹Ç©Ä¾á¸¿êž‘ȯð˜±ð‘žð—‹ð˜´È¶ðž„ðœˆÏˆð’™ð˜†ðš£1234567890!@#$%^&*()-_=+[{]};:'",<.>/?~ -ð˜ˆá¸†ð–¢ð•¯Ù¤á¸žÔÐÇð™…ƘԸⲘð™‰à§¦Î¡ð—¤ÉŒð“¢ÈšÐ¦ð’±Ñ ð“§Æ³È¤Ñ§á–¯Ä‡ð—±á»…ð‘“ð™œá‚¹ðž²ð‘—ð’ŒÄ¼á¹ƒÅ‰Ð¾ðžŽð’’ᵲꜱð™©á»«ð—ŵð’™ð’šÅº1234567890!@#$%^&*( -)-_=+[{]};:'",<.>/?~ÐḂⲤð——ð–¤ð—™êž ê“§ÈŠð‰ðœ¥ê“¡ð‘€ð‘µÇ¬ð™¿ð‘„Å–ð‘†ð’¯ð–´ð˜ð˜žê“«Å¸ðœ¡áº£ð˜¢Æ€ð–¼á¸‹áº¿áµ®â„Šð™áŽ¥ð•›ÐºÎ¹á¹ƒÕ¤â±ºð“…ð˜²ð•£ð–˜Å§ð‘¢á¹½áº‰ð˜… -ყž1234567890!@#$%^&*()-_=+[{]};:'",<.>/?~Ѧð™±Æ‡á—žÎ£â„±ÔÒ¤Ù¡ð”Кð“›ð“œÆÈŽðš¸ð‘„Ṛð“¢á¹®á¹ºÆ²á”ê“«ðšˆðš­ðœ¶ -áçძð‘’ð–¿ð—€á¸§ð—‚ð£Òɭḿð•Ÿð¨ð”ð•¢á¹›ð“¼Ñ‚úð”³áºƒâ¤¬ð²ð—“1234567890!@#$%^&*()-_=+[{]};:'",<.>/?~ð– Î’ð’žð˜‹ð™´ð“•Ä¢Èž -Ỉð•µê“—ÊŸð™¼â„•à§¦ðš¸ð—¤Õ€ê“¢á¹°Ç“â…¤ð”šâ²¬ð‘Œð™•ð˜¢ð•¤ """ - reporterinstance(msg) - - -def test_mojibake_file(request): - testdir = request.fspath.dirname - with open(os.path.join(testdir, "test_ancils", "unicode.txt")) as fhandle: - data = fhandle.read() - reporterinstance = Reporter() - reporterinstance(data) diff --git a/lib/python/rose/__init__.py b/metomi/rose/__init__.py similarity index 99% rename from lib/python/rose/__init__.py rename to metomi/rose/__init__.py index f740cd6f38..04b2dc2c3c 100644 --- a/lib/python/rose/__init__.py +++ b/metomi/rose/__init__.py @@ -127,3 +127,5 @@ # Paths in the Rose distribution. FILEPATH_README = "README.md" + +__version__ = "2.0.0alpha3" diff --git a/lib/python/rose/app_run.py b/metomi/rose/app_run.py similarity index 96% rename from lib/python/rose/app_run.py rename to metomi/rose/app_run.py index 04b53ea415..3caf20f041 100644 --- a/lib/python/rose/app_run.py +++ b/metomi/rose/app_run.py @@ -28,14 +28,14 @@ from isodatetime.data import get_timepoint_for_now from isodatetime.parsers import ISO8601SyntaxError -from rose.config import ConfigDumper -from rose.date import RoseDateTimeOperator -from rose.env import env_var_process, UnboundEnvironmentVariableError -from rose.opt_parse import RoseOptionParser -from rose.popen import RosePopenError -from rose.reporter import Event, Reporter -from rose.run import Runner -from rose.scheme_handler import SchemeHandlersManager +from metomi.rose.config import ConfigDumper +from metomi.rose.date import RoseDateTimeOperator +from metomi.rose.env import env_var_process, UnboundEnvironmentVariableError +from metomi.rose.opt_parse import RoseOptionParser +from metomi.rose.popen import RosePopenError +from metomi.rose.reporter import Event, Reporter +from metomi.rose.run import Runner +from metomi.rose.scheme_handler import SchemeHandlersManager class ConfigValueError(Exception): @@ -292,7 +292,7 @@ class BuiltinApp(object): """An abstract base class for a builtin application. Instance of sub-classes are expected to be managed by - rose.scheme_handler.SchemeHandlersManager. + metomi.rose.scheme_handler.SchemeHandlersManager. """ @@ -332,7 +332,8 @@ class AppRunner(Runner): def __init__(self, *args, **kwargs): Runner.__init__(self, *args, **kwargs) - path = os.path.dirname(os.path.dirname(sys.modules["rose"].__file__)) + path = os.path.dirname( + os.path.dirname(sys.modules["metomi.rose"].__file__)) self.builtins_manager = SchemeHandlersManager( [path], "rose.apps", ["run"], None, *args, **kwargs) self.date_time_oper = RoseDateTimeOperator() diff --git a/lib/python/rose/apps/__init__.py b/metomi/rose/apps/__init__.py similarity index 100% rename from lib/python/rose/apps/__init__.py rename to metomi/rose/apps/__init__.py diff --git a/etc/rose-meta/rose-demo-baked-alaska-sponge/vn1.0/lib/python/macros/__init__.py b/metomi/rose/apps/ana_builtin/__init__.py similarity index 100% rename from etc/rose-meta/rose-demo-baked-alaska-sponge/vn1.0/lib/python/macros/__init__.py rename to metomi/rose/apps/ana_builtin/__init__.py diff --git a/lib/python/rose/apps/ana_builtin/grepper.py b/metomi/rose/apps/ana_builtin/grepper.py similarity index 99% rename from lib/python/rose/apps/ana_builtin/grepper.py rename to metomi/rose/apps/ana_builtin/grepper.py index 947a305fa0..aa45721b2d 100644 --- a/lib/python/rose/apps/ana_builtin/grepper.py +++ b/metomi/rose/apps/ana_builtin/grepper.py @@ -42,8 +42,8 @@ import os import re -from rose import TYPE_LOGICAL_VALUE_TRUE -from rose.apps.rose_ana import AnalysisTask +from metomi.rose import TYPE_LOGICAL_VALUE_TRUE +from metomi.rose.apps.rose_ana import AnalysisTask class SingleCommandStatus(AnalysisTask): diff --git a/lib/python/rose/apps/comparisons/cumf.py b/metomi/rose/apps/comparisons/cumf.py similarity index 100% rename from lib/python/rose/apps/comparisons/cumf.py rename to metomi/rose/apps/comparisons/cumf.py diff --git a/lib/python/rose/apps/comparisons/exact.py b/metomi/rose/apps/comparisons/exact.py similarity index 98% rename from lib/python/rose/apps/comparisons/exact.py rename to metomi/rose/apps/comparisons/exact.py index a6e329f4ac..d6b5d565f7 100644 --- a/lib/python/rose/apps/comparisons/exact.py +++ b/metomi/rose/apps/comparisons/exact.py @@ -19,7 +19,7 @@ # ----------------------------------------------------------------------------- """Compare two lists of numbers exactly.""" -from rose.apps.rose_ana_v1 import DataLengthError +from metomi.rose.apps.rose_ana_v1 import DataLengthError OUTPUT_STRING = "%s %s: %s%%: File %s %s %s (%s values)" PASS = "==" diff --git a/lib/python/rose/apps/comparisons/mandatory.py b/metomi/rose/apps/comparisons/mandatory.py similarity index 100% rename from lib/python/rose/apps/comparisons/mandatory.py rename to metomi/rose/apps/comparisons/mandatory.py diff --git a/lib/python/rose/apps/comparisons/output_grepper.py b/metomi/rose/apps/comparisons/output_grepper.py similarity index 96% rename from lib/python/rose/apps/comparisons/output_grepper.py rename to metomi/rose/apps/comparisons/output_grepper.py index 16988207b6..2fbb890943 100644 --- a/lib/python/rose/apps/comparisons/output_grepper.py +++ b/metomi/rose/apps/comparisons/output_grepper.py @@ -19,7 +19,7 @@ # ----------------------------------------------------------------------------- """Return a list of values matching a regular expression.""" -from rose.apps.rose_ana_v1 import data_from_regexp +from metomi.rose.apps.rose_ana_v1 import data_from_regexp REGEXPS = { 'um_wallclock': r"Maximum Elapsed Wallclock Time:\s*(\S+)", diff --git a/lib/python/rose/apps/comparisons/prohibited.py b/metomi/rose/apps/comparisons/prohibited.py similarity index 100% rename from lib/python/rose/apps/comparisons/prohibited.py rename to metomi/rose/apps/comparisons/prohibited.py diff --git a/lib/python/rose/apps/comparisons/within.py b/metomi/rose/apps/comparisons/within.py similarity index 98% rename from lib/python/rose/apps/comparisons/within.py rename to metomi/rose/apps/comparisons/within.py index be5f321fdf..e89fa0c989 100644 --- a/lib/python/rose/apps/comparisons/within.py +++ b/metomi/rose/apps/comparisons/within.py @@ -20,7 +20,7 @@ """Compare one list of numbers is within a tolerance of a second.""" import re -from rose.apps.rose_ana_v1 import DataLengthError +from metomi.rose.apps.rose_ana_v1 import DataLengthError OUTPUT_STRING = "%(extract)s %(percent)s%% %(sign)s %(tolerance)s: " + \ "File %(file1)s c.f. %(file2)s (%(numvals)s values)" diff --git a/lib/python/rose/apps/fcm_make.py b/metomi/rose/apps/fcm_make.py similarity index 98% rename from lib/python/rose/apps/fcm_make.py rename to metomi/rose/apps/fcm_make.py index 616edba2ba..6f484c3530 100644 --- a/lib/python/rose/apps/fcm_make.py +++ b/metomi/rose/apps/fcm_make.py @@ -25,11 +25,11 @@ import sys from tempfile import mkdtemp -from rose.env import ( +from metomi.rose.env import ( env_export, env_var_process, UnboundEnvironmentVariableError) -from rose.app_run import BuiltinApp, ConfigValueError -from rose.fs_util import FileSystemEvent -from rose.popen import RosePopenError +from metomi.rose.app_run import BuiltinApp, ConfigValueError +from metomi.rose.fs_util import FileSystemEvent +from metomi.rose.popen import RosePopenError ORIG = 0 CONT = 1 diff --git a/lib/python/rose/apps/rose_ana.py b/metomi/rose/apps/rose_ana.py similarity index 94% rename from lib/python/rose/apps/rose_ana.py rename to metomi/rose/apps/rose_ana.py index 4379788cad..22219d58cc 100644 --- a/lib/python/rose/apps/rose_ana.py +++ b/metomi/rose/apps/rose_ana.py @@ -34,16 +34,17 @@ from contextlib import contextmanager # Rose modules -from rose import TYPE_LOGICAL_VALUE_TRUE -from rose.reporter import Reporter -from rose.resource import ResourceLocator -from rose.app_run import BuiltinApp -from rose.env import env_var_process +from metomi.rose import TYPE_LOGICAL_VALUE_TRUE +from metomi.rose.reporter import Reporter +from metomi.rose.resource import ResourceLocator +from metomi.rose.app_run import BuiltinApp +from metomi.rose.env import env_var_process class KGODatabase(object): """ - KGO Database object, stores comparison information for rose_ana apps. + KGO Database object, stores comparison information for metomi.rose_ana + apps. """ # This SQL command ensures a "comparisons" table exists in the database @@ -87,8 +88,8 @@ def enter_comparison( sql_statement = ( "INSERT OR REPLACE INTO comparisons VALUES (?, ?, ?, ?, ?)") # Prepend the task_name onto each entry, to try and ensure it is - # unique (the individual comparison names may not be, but the rose - # task name + the comparison task name should) + # unique (the individual comparison names may not be, but the + # rose task name + the comparison task name should) sql_args = [self.task_name + " - " + comp_task, kgo_file, suite_file, status, comparison] # Add the command and arguments to the buffer @@ -243,17 +244,17 @@ def run(self, app_runner, conf_tree, opts, args, uuid, work_files): self.config = conf_tree.node self.app_runner = app_runner - # Attach to the main rose config (for retrieving settings from things - # like the user's ~/.metomi/rose.conf) + # Attach to the main rose config (for retrieving settings from + # things like the user's ~/.metomi/rose.conf) self.rose_conf = ResourceLocator.default().get_conf() # Attach to a reporter instance for sending messages. self._init_reporter(app_runner.event_handler) - # As part of the introduction of a re-written rose_ana, backwards - # compatibility is maintained here by detecting the lack of the - # newer syntax in the app config and falling back to the old version - # of the rose_ana app (renamed to rose_ana_v1) + # As part of the introduction of a re-written rose_ana, + # backwards compatibility is maintained here by detecting the lack of + # the newer syntax in the app config and falling back to the old + # version of the rose_ana app (renamed to rose_ana_v1) # **Once the old behaviour is removed the below block can be too**. new_style_app = False for keys, _ in self.config.walk(no_ignore=True): @@ -263,15 +264,16 @@ def run(self, app_runner, conf_tree, opts, args, uuid, work_files): break if not new_style_app: # Use the previous app by instantiating and calling it explicitly - self.reporter("!!WARNING!! - Detected old style rose_ana app; " - "Using previous rose_ana version...") - from rose.apps.rose_ana_v1 import RoseAnaV1App + self.reporter( + "!!WARNING!! - Detected old style rose_ana app; " + "Using previous rose_ana version...") + from metomi.rose.apps.rose_ana_v1 import RoseAnaV1App old_app = RoseAnaV1App(manager=self.manager) return old_app.run( app_runner, conf_tree, opts, args, uuid, work_files) - # Load any rose_ana specific configuration settings either from the - # site defaults or the user's personal config + # Load any rose_ana specific configuration settings either from + # the site defaults or the user's personal config self._get_global_ana_config() # If the user's config indicates that it should be used - attach @@ -301,9 +303,9 @@ def run(self, app_runner, conf_tree, opts, args, uuid, work_files): self.titlebar("Running task #{0}".format(itask + 1)) self.reporter("Method: {0}".format(task.options["full_task_name"])) - # Since the run_analysis method is out of rose's control in many - # cases the safest thing to do is a blanket try/except; since we - # have no way of knowing what exceptions might be raised. + # Since the run_analysis method is out of rose's control in + # many cases the safest thing to do is a blanket try/except; since + # we have no way of knowing what exceptions might be raised. try: task.run_analysis() # In the case that the task didn't raise any exception, @@ -392,8 +394,8 @@ def titlebar(self, title): self.reporter("{0} {1} {0}".format("*" * int(sidebarlen), title)) def _get_global_ana_config(self): - """Retrieves all rose_ana config options; these could be from the - site's settings or the user's personal settings.""" + """Retrieves all rose_ana config options; these could be from + the site's settings or the user's personal settings.""" self.ana_config = {} user_config = ( self.rose_conf.get_value(["rose-ana"])) @@ -481,10 +483,10 @@ def _load_tasks(self): task = task.split(":", 1)[1] if len(keys) == 2: - # The app may define a section containing rose_ana config - # settings; add these to the config dictionary (if any of - # the names match existing config options from the global - # config it will be overwritten) + # The app may define a section containing rose_ana + # config settings; add these to the config dictionary (if + # any) of the names match existing config options from the + # global config it will be overwritten) if task == "config": self.ana_config[keys[1]] = node.value continue @@ -573,7 +575,8 @@ def _get_method_paths(self): if os.path.exists(ana_dir): method_paths.append(ana_dir) - # The rose config can specify a directory for site-specific methods + # The rose config can specify a directory for site-specific + # methods config_paths = self.rose_conf.get_value(["rose-ana", "method-path"]) if config_paths: for config_dir in config_paths.split(): diff --git a/lib/python/rose/apps/rose_ana_v1.py b/metomi/rose/apps/rose_ana_v1.py similarity index 98% rename from lib/python/rose/apps/rose_ana_v1.py rename to metomi/rose/apps/rose_ana_v1.py index 1851d82981..6db42eb3e7 100644 --- a/lib/python/rose/apps/rose_ana_v1.py +++ b/metomi/rose/apps/rose_ana_v1.py @@ -30,12 +30,12 @@ import time # Rose modules -import rose.config -from rose.env import env_var_process -from rose.popen import RosePopener -from rose.reporter import Reporter, Event -from rose.resource import ResourceLocator -from rose.app_run import BuiltinApp +import metomi.rose.config +from metomi.rose.env import env_var_process +from metomi.rose.popen import RosePopener +from metomi.rose.reporter import Reporter, Event +from metomi.rose.resource import ResourceLocator +from metomi.rose.app_run import BuiltinApp WARN = -1 PASS = 0 @@ -52,7 +52,6 @@ class KGODatabase(object): """ KGO Database object, stores comparison information for rose_ana apps. - """ # Stores retries of database creation and the maximum allowed number # of retries before an exception is raised @@ -345,7 +344,7 @@ def do_extract(self, task, var): return task def _run_command(self, command): - """Run an external command using rose.popen.""" + """Run an external command using metomi.rose.popen.""" output = self.popen.run_ok(command, shell=True)[0] output = "".join(output).splitlines() return output @@ -473,7 +472,7 @@ def load_user_comparison_modules(self, files): def write_config(self, filename, tasks): """Write an analysis config file based on a list of tasks provided""" - config = rose.config.ConfigNode() + config = metomi.rose.config.ConfigNode() for task in tasks: sectionname = task.name @@ -495,7 +494,7 @@ def write_config(self, filename, tasks): config.set([sectionname, "tolerance"], task.tolerance) if task.warnonfail: config.set([sectionname, "warnonfail"], "true") - rose.config.dump(config, filename) + metomi.rose.config.dump(config, filename) class AnalysisTask(object): diff --git a/lib/python/rose/apps/rose_arch.py b/metomi/rose/apps/rose_arch.py similarity index 98% rename from lib/python/rose/apps/rose_arch.py rename to metomi/rose/apps/rose_arch.py index 6bd92d6128..2a0c8d7207 100644 --- a/lib/python/rose/apps/rose_arch.py +++ b/metomi/rose/apps/rose_arch.py @@ -23,15 +23,15 @@ from glob import glob import os import re -from rose.app_run import ( +from metomi.rose.app_run import ( BuiltinApp, ConfigValueError, CompulsoryConfigValueError) -from rose.checksum import get_checksum, get_checksum_func -from rose.env import env_var_process, UnboundEnvironmentVariableError -from rose.popen import RosePopenError -from rose.reporter import Event, Reporter -from rose.scheme_handler import SchemeHandlersManager +from metomi.rose.checksum import get_checksum, get_checksum_func +from metomi.rose.env import env_var_process, UnboundEnvironmentVariableError +from metomi.rose.popen import RosePopenError +from metomi.rose.reporter import Event, Reporter +from metomi.rose.scheme_handler import SchemeHandlersManager import shlex import sqlite3 import sys diff --git a/lib/python/rose/apps/rose_arch_compressions/__init__.py b/metomi/rose/apps/rose_arch_compressions/__init__.py similarity index 100% rename from lib/python/rose/apps/rose_arch_compressions/__init__.py rename to metomi/rose/apps/rose_arch_compressions/__init__.py diff --git a/lib/python/rose/apps/rose_arch_compressions/rose_arch_gzip.py b/metomi/rose/apps/rose_arch_compressions/rose_arch_gzip.py similarity index 100% rename from lib/python/rose/apps/rose_arch_compressions/rose_arch_gzip.py rename to metomi/rose/apps/rose_arch_compressions/rose_arch_gzip.py diff --git a/lib/python/rose/apps/rose_arch_compressions/rose_arch_tar.py b/metomi/rose/apps/rose_arch_compressions/rose_arch_tar.py similarity index 100% rename from lib/python/rose/apps/rose_arch_compressions/rose_arch_tar.py rename to metomi/rose/apps/rose_arch_compressions/rose_arch_tar.py diff --git a/lib/python/rose/apps/rose_bunch.py b/metomi/rose/apps/rose_bunch.py similarity index 95% rename from lib/python/rose/apps/rose_bunch.py rename to metomi/rose/apps/rose_bunch.py index 36f3cb78f4..4288d3f4a8 100644 --- a/lib/python/rose/apps/rose_bunch.py +++ b/metomi/rose/apps/rose_bunch.py @@ -17,7 +17,8 @@ # You should have received a copy of the GNU General Public License # along with Rose. If not, see . # ---------------------------------------------------------------------------- -"""Builtin application: rose_bunch: run multiple commands in parallel.""" +"""Builtin application: rose_bunch: run multiple commands in parallel. +""" import itertools @@ -26,12 +27,12 @@ import sqlite3 from time import sleep -from rose.app_run import ( +from metomi.rose.app_run import ( BuiltinApp, ConfigValueError) -from rose.popen import RosePopenError -import rose.job_runner -from rose.reporter import Event +from metomi.rose.popen import RosePopenError +import metomi.rose.job_runner +from metomi.rose.reporter import Event class CommandNotDefinedError(Exception): @@ -141,14 +142,15 @@ def run(self, app_runner, conf_tree, opts, args, uuid, work_files): "names"]) if self.invocation_names: self.invocation_names = shlex.split( - rose.env.env_var_process(self.invocation_names)) + metomi.rose.env.env_var_process(self.invocation_names)) if len(set(self.invocation_names)) != len(self.invocation_names): raise ConfigValueError([self.BUNCH_SECTION, "names"], self.invocation_names, "names must be unique") - self.fail_mode = rose.env.env_var_process(conf_tree.node.get_value( - [self.BUNCH_SECTION, "fail-mode"], self.TYPE_CONTINUE_ON_FAIL)) + self.fail_mode = metomi.rose.env.env_var_process( + conf_tree.node.get_value( + [self.BUNCH_SECTION, "fail-mode"], self.TYPE_CONTINUE_ON_FAIL)) if self.fail_mode not in self.FAIL_MODE_TYPES: raise ConfigValueError([self.BUNCH_SECTION, "fail-mode"], @@ -159,10 +161,11 @@ def run(self, app_runner, conf_tree, opts, args, uuid, work_files): "incremental"], "true") if self.incremental: - self.incremental = rose.env.env_var_process(self.incremental) + self.incremental = metomi.rose.env.env_var_process( + self.incremental) self.isformatted = True - self.command = rose.env.env_var_process( + self.command = metomi.rose.env.env_var_process( conf_tree.node.get_value([self.BUNCH_SECTION, "command-format"])) if not self.command: @@ -178,7 +181,8 @@ def run(self, app_runner, conf_tree, opts, args, uuid, work_files): if instances: try: - instances = range(int(rose.env.env_var_process(instances))) + instances = range( + int(metomi.rose.env.env_var_process(instances))) except ValueError: raise ConfigValueError([self.BUNCH_SECTION, "command-instances"], @@ -192,7 +196,7 @@ def run(self, app_runner, conf_tree, opts, args, uuid, work_files): for key, val in multi_args.items(): bunch_args_names.append(key) bunch_args_values.append( - shlex.split(rose.env.env_var_process(val.value))) + shlex.split(metomi.rose.env.env_var_process(val.value))) # Update the argument values based on the argument-mode argument_mode = conf_tree.node.get_value([self.BUNCH_SECTION, @@ -263,7 +267,7 @@ def run(self, app_runner, conf_tree, opts, args, uuid, work_files): max_procs = conf_tree.node.get_value([self.BUNCH_SECTION, "pool-size"]) if max_procs: - max_procs = int(rose.env.env_var_process(max_procs)) + max_procs = int(metomi.rose.env.env_var_process(max_procs)) else: max_procs = arglength diff --git a/lib/python/rose/apps/rose_prune.py b/metomi/rose/apps/rose_prune.py similarity index 97% rename from lib/python/rose/apps/rose_prune.py rename to metomi/rose/apps/rose_prune.py index c5e8409a31..981d5df67e 100644 --- a/lib/python/rose/apps/rose_prune.py +++ b/metomi/rose/apps/rose_prune.py @@ -21,12 +21,12 @@ import os from random import shuffle -from rose.app_run import BuiltinApp, ConfigValueError -from rose.date import RoseDateTimeOperator -from rose.env import env_var_process, UnboundEnvironmentVariableError -from rose.fs_util import FileSystemEvent -from rose.host_select import HostSelector -from rose.popen import RosePopenError +from metomi.rose.app_run import BuiltinApp, ConfigValueError +from metomi.rose.date import RoseDateTimeOperator +from metomi.rose.env import env_var_process, UnboundEnvironmentVariableError +from metomi.rose.fs_util import FileSystemEvent +from metomi.rose.host_select import HostSelector +from metomi.rose.popen import RosePopenError import shlex diff --git a/lib/python/rose/bush_dao.py b/metomi/rose/bush_dao.py similarity index 99% rename from lib/python/rose/bush_dao.py rename to metomi/rose/bush_dao.py index d6bafa7265..88b48e2572 100644 --- a/lib/python/rose/bush_dao.py +++ b/metomi/rose/bush_dao.py @@ -25,7 +25,7 @@ import re import tarfile -from rose.suite_engine_procs.cylc import CylcProcessor, CylcSuiteDAO +from metomi.rose.suite_engine_procs.cylc import CylcProcessor, CylcSuiteDAO class RoseBushDAO(object): diff --git a/lib/python/rose/c3.py b/metomi/rose/c3.py similarity index 100% rename from lib/python/rose/c3.py rename to metomi/rose/c3.py diff --git a/lib/python/rose/checksum.py b/metomi/rose/checksum.py similarity index 99% rename from lib/python/rose/checksum.py rename to metomi/rose/checksum.py index cfaef2b4c3..0f0fec5949 100644 --- a/lib/python/rose/checksum.py +++ b/metomi/rose/checksum.py @@ -25,7 +25,7 @@ import inspect import os -from rose.resource import ResourceLocator +from metomi.rose.resource import ResourceLocator _DEFAULT_DEFAULT_KEY = "md5" diff --git a/lib/python/rose/cmp_source_vc.py b/metomi/rose/cmp_source_vc.py similarity index 92% rename from lib/python/rose/cmp_source_vc.py rename to metomi/rose/cmp_source_vc.py index d31a6036c4..e2f2f88d8b 100644 --- a/lib/python/rose/cmp_source_vc.py +++ b/metomi/rose/cmp_source_vc.py @@ -25,11 +25,11 @@ import sys import traceback -from rose.opt_parse import RoseOptionParser -from rose.popen import RosePopener, RosePopenError -from rose.reporter import Reporter -from rose.run_source_vc import write_source_vc_info -from rose.suite_engine_proc import SuiteEngineProcessor +from metomi.rose.opt_parse import RoseOptionParser +from metomi.rose.popen import RosePopener +from metomi.rose.reporter import Reporter +from metomi.rose.run_source_vc import write_source_vc_info +from metomi.rose.suite_engine_proc import SuiteEngineProcessor class SuiteVCComparator(object): @@ -96,7 +96,8 @@ def main(): else: if lines is None: event_handler( - '%s: rose-suite-run.version: VC info not found' % (suite_name), + '%s: rose-suite-run.version: VC info not found' % ( + suite_name), kind=Reporter.KIND_ERR, level=Reporter.FAIL) sys.exit(2) lines = list(line for line in lines) diff --git a/lib/python/rose/config.py b/metomi/rose/config.py similarity index 99% rename from lib/python/rose/config.py rename to metomi/rose/config.py index dd55237546..0afe963ce2 100644 --- a/lib/python/rose/config.py +++ b/metomi/rose/config.py @@ -22,9 +22,9 @@ .. testsetup:: * import os - from rose.config import * + from metomi.rose.config import * -.. testcleanup:: rose.config +.. testcleanup:: metomi.rose.config try: os.remove('config.conf') @@ -68,15 +68,15 @@ Classes: .. autosummary:: - rose.config.ConfigNode - rose.config.ConfigNodeDiff - rose.config.ConfigDumper - rose.config.ConfigLoader + metomi.rose.config.ConfigNode + metomi.rose.config.ConfigNodeDiff + metomi.rose.config.ConfigDumper + metomi.rose.config.ConfigLoader Functions: .. autosummary:: - rose.config.load - rose.config.dump + metomi.rose.config.load + metomi.rose.config.dump Limitations: - The loader does not handle trailing comments. @@ -93,12 +93,12 @@ import copy import os.path import re -from rose.env import env_var_escape +from metomi.rose.env import env_var_escape import shlex import sys from tempfile import NamedTemporaryFile, SpooledTemporaryFile from functools import cmp_to_key -from rose.unicode_utils import write_safely +from metomi.rose.unicode_utils import write_safely CHAR_ASSIGN = "=" @@ -1234,7 +1234,7 @@ def load_with_opts(self, source, node=None, more_keys=None, pairs. Only returned if return_config_map is True. Examples: - .. testcleanup:: rose.config.ConfigLoader.load_with_opts + .. testcleanup:: metomi.rose.config.ConfigLoader.load_with_opts try: os.remove('config.conf') diff --git a/lib/python/rose/config_cli.py b/metomi/rose/config_cli.py similarity index 92% rename from lib/python/rose/config_cli.py rename to metomi/rose/config_cli.py index bb5e6e792c..c7d9afe7a6 100644 --- a/lib/python/rose/config_cli.py +++ b/metomi/rose/config_cli.py @@ -19,13 +19,13 @@ # ----------------------------------------------------------------------------- """Implements the "rose config" command.""" -from rose.config import ( +from metomi.rose.config import ( ConfigDumper, ConfigLoader, ConfigNode, ConfigSyntaxError) -from rose.env import env_var_process -from rose.opt_parse import RoseOptionParser -from rose.reporter import Reporter, Event -from rose.resource import ResourceLocator -import rose.macro +from metomi.rose.env import env_var_process +from metomi.rose.opt_parse import RoseOptionParser +from metomi.rose.reporter import Reporter, Event +from metomi.rose.resource import ResourceLocator +import metomi.rose.macro import os import sys @@ -48,8 +48,9 @@ def get_meta_path(root_node, rel_path=None, meta_key=False): dir_path = os.path.abspath(rel_path) else: dir_path = os.getcwd() - meta_dir = rose.macro.load_meta_path(config=root_node, - directory=dir_path)[0] + meta_dir = metomi.rose.macro.load_meta_path( + config=root_node, + directory=dir_path)[0] if meta_dir is None: return None else: @@ -65,7 +66,7 @@ def main(): opts, args = opt_parser.parse_args() report = Reporter(opts.verbosity - opts.quietness) - rose.macro.add_meta_paths() + metomi.rose.macro.add_meta_paths() if opts.meta_key: opts.meta = True @@ -102,7 +103,7 @@ def main(): root_node.set(["meta"], opts.meta_key) else: fname = os.path.join( - os.getcwd(), rose.SUB_CONFIG_NAME) + os.getcwd(), metomi.rose.SUB_CONFIG_NAME) try: root_node = config_loader.load(fname) except ConfigSyntaxError as exc: diff --git a/lib/python/rose/config_diff.py b/metomi/rose/config_diff.py similarity index 82% rename from lib/python/rose/config_diff.py rename to metomi/rose/config_diff.py index 36652e80e6..42229fd607 100644 --- a/lib/python/rose/config_diff.py +++ b/metomi/rose/config_diff.py @@ -27,13 +27,13 @@ import sys import tempfile -import rose.config -import rose.fs_util -import rose.macro -import rose.opt_parse -import rose.popen -import rose.resource -import rose.run +import metomi.rose.config +import metomi.rose.fs_util +import metomi.rose.macro +import metomi.rose.opt_parse +import metomi.rose.popen +import metomi.rose.resource +import metomi.rose.run class ConfigDiffDefaults(object): @@ -41,8 +41,8 @@ class ConfigDiffDefaults(object): """Store default settings for the rose config-diff command.""" PROPERTIES = ",".join( - [rose.META_PROP_TITLE, rose.META_PROP_NS, - rose.META_PROP_DESCRIPTION, rose.META_PROP_HELP] + [metomi.rose.META_PROP_TITLE, metomi.rose.META_PROP_NS, + metomi.rose.META_PROP_DESCRIPTION, metomi.rose.META_PROP_HELP] ) SHORTHAND = [] @@ -52,11 +52,11 @@ class ConfigDiffDefaults(object): def annotate_config_with_metadata(config, meta_config, ignore_regexes=None, metadata_properties=None): - """Add metadata to the rose.config.ConfigNode.comments attribute. + """Add metadata to the metomi.rose.config.ConfigNode.comments attribute. - config -- a rose.config.ConfigNode instance, containing app or + config -- a metomi.rose.config.ConfigNode instance, containing app or suite data. - meta_config -- a rose.config.ConfigNode instance, containing + meta_config -- a metomi.rose.config.ConfigNode instance, containing metadata for config. ignore_regexes -- (default None) a list of uncompiled regular expressions - if a setting contains any of these, @@ -73,11 +73,12 @@ def annotate_config_with_metadata(config, meta_config, ignore_regexes=None, option = None if len(keylist) > 1: option = keylist[1] - id_ = rose.macro.get_id_from_section_option(section, option) + id_ = metomi.rose.macro.get_id_from_section_option(section, option) if any(_.search(id_) for _ in ignore_recs): unset_keys.append(keylist) continue - metadata = rose.macro.get_metadata_for_config_id(id_, meta_config) + metadata = metomi.rose.macro.get_metadata_for_config_id( + id_, meta_config) metadata_text = format_metadata_as_text( metadata, only_these_options=metadata_properties) metadata_lines = [" " + line for line in metadata_text.splitlines()] @@ -113,7 +114,7 @@ def format_metadata_as_text(metadata, only_these_options=None): metadata keys. Otherwise, output all metadata keys. """ - id_node = rose.config.ConfigNode() + id_node = metomi.rose.config.ConfigNode() if only_these_options is None: # Default to every option. only_these_options = sorted(metadata.keys()) @@ -123,28 +124,28 @@ def format_metadata_as_text(metadata, only_these_options=None): continue id_node.set([property_], value=value) string_file = io.StringIO() - rose.config.dump(id_node, target=string_file) + metomi.rose.config.dump(id_node, target=string_file) return string_file.getvalue() def main(): """Implement the "rose config-diff" command.""" - opt_parser = rose.opt_parse.RoseOptionParser() + opt_parser = metomi.rose.opt_parse.RoseOptionParser() opt_parser.add_my_options("diff_tool", "graphical", "ignore", "meta_path", "properties", "opt_conf_keys_1", "opt_conf_keys_2") my_sys_args = list(sys.argv) opts, args = opt_parser.parse_args(my_sys_args[1:]) - rose.macro.add_meta_paths() - rose.macro.add_opt_meta_paths(opts.meta_path) + metomi.rose.macro.add_meta_paths() + metomi.rose.macro.add_opt_meta_paths(opts.meta_path) paths, diff_args = args[:2], args[2:] if len(paths) != 2: sys.exit(opt_parser.get_usage()) - config_loader = rose.config.ConfigLoader() + config_loader = metomi.rose.config.ConfigLoader() if opts.properties is None: properties = _DEFAULTS.PROPERTIES.split(",") @@ -155,7 +156,7 @@ def main(): # get file paths output_filenames = [] - config_type = rose.SUB_CONFIG_NAME + config_type = metomi.rose.SUB_CONFIG_NAME file_paths = [] for path in paths: if path == "-": @@ -163,15 +164,15 @@ def main(): continue path = os.path.abspath(path) if os.path.isdir(path): - for filename in rose.CONFIG_NAMES: + for filename in metomi.rose.CONFIG_NAMES: file_path = os.path.join(path, filename) if os.path.isfile(file_path): config_type = filename file_paths.append(file_path) break else: - raise rose.run.ConfigNotFoundError( - path, rose.GLOB_CONFIG_FILE) + raise metomi.rose.run.ConfigNotFoundError( + path, metomi.rose.GLOB_CONFIG_FILE) else: config_type = os.path.basename(path) file_paths.append(path) @@ -193,10 +194,10 @@ def main(): directory, filename = path.rsplit(os.sep, 1) config = config_loader.load_with_opts(path, mark_opt_confs=True, more_keys=opt_conf_keys) - meta_config_tree = rose.macro.load_meta_config_tree( + meta_config_tree = metomi.rose.macro.load_meta_config_tree( config, directory=directory, config_type=filename) if meta_config_tree is None: - meta_config = rose.config.ConfigNode() + meta_config = metomi.rose.config.ConfigNode() else: meta_config = meta_config_tree.node annotated_config = annotate_config_with_metadata( @@ -205,9 +206,9 @@ def main(): ) output_dir = tempfile.mkdtemp() output_path = os.path.join(output_dir, filename) - rose.config.dump(annotated_config, target=output_path) + metomi.rose.config.dump(annotated_config, target=output_path) output_filenames.append(output_path) - popener = rose.popen.RosePopener() + popener = metomi.rose.popen.RosePopener() cmd_opts_args = diff_args + output_filenames if opts.diff_tool is None: if opts.graphical_mode: @@ -222,7 +223,7 @@ def main(): sys.stdout.buffer.write(stdout) sys.stderr.buffer.write(stderr) finally: - fs_util = rose.fs_util.FileSystemUtil() + fs_util = metomi.rose.fs_util.FileSystemUtil() for path in output_filenames: fs_util.delete(os.path.dirname(path)) sys.exit(return_code) @@ -230,7 +231,7 @@ def main(): def load_override_config(): """Load user or site options for the config_diff command.""" - conf = rose.resource.ResourceLocator.default().get_conf().get( + conf = metomi.rose.resource.ResourceLocator.default().get_conf().get( ["rose-config-diff"]) if conf is None: return diff --git a/lib/python/rose/config_dump.py b/metomi/rose/config_dump.py similarity index 89% rename from lib/python/rose/config_dump.py rename to metomi/rose/config_dump.py index f772cdfcc0..9c35fee30a 100644 --- a/lib/python/rose/config_dump.py +++ b/metomi/rose/config_dump.py @@ -24,12 +24,12 @@ import fnmatch import os -from rose import META_CONFIG_NAME -from rose.config import ConfigDumper, ConfigLoader -from rose.fs_util import FileSystemUtil -from rose.macro import pretty_format_config -from rose.opt_parse import RoseOptionParser -from rose.reporter import Event, Reporter +from metomi.rose import META_CONFIG_NAME +from metomi.rose.config import ConfigDumper, ConfigLoader +from metomi.rose.fs_util import FileSystemUtil +from metomi.rose.macro import pretty_format_config +from metomi.rose.opt_parse import RoseOptionParser +from metomi.rose.reporter import Event, Reporter from tempfile import NamedTemporaryFile diff --git a/lib/python/rose/config_processor.py b/metomi/rose/config_processor.py similarity index 90% rename from lib/python/rose/config_processor.py rename to metomi/rose/config_processor.py index 7bda92dba8..4f6abc5022 100644 --- a/lib/python/rose/config_processor.py +++ b/metomi/rose/config_processor.py @@ -17,13 +17,13 @@ # You should have received a copy of the GNU General Public License # along with Rose. If not, see . # ----------------------------------------------------------------------------- -"""Process named settings in rose.config.ConfigTree.""" +"""Process named settings in metomi.rose.config.ConfigTree.""" import os -from rose.env import UnboundEnvironmentVariableError -from rose.fs_util import FileSystemUtil -from rose.popen import RosePopener -from rose.scheme_handler import SchemeHandlersManager +from metomi.rose.env import UnboundEnvironmentVariableError +from metomi.rose.fs_util import FileSystemUtil +from metomi.rose.popen import RosePopener +from metomi.rose.scheme_handler import SchemeHandlersManager import sys @@ -85,8 +85,8 @@ def process(self, conf_tree, item, orig_keys=None, orig_value=None, Arguments: conf_tree: - The relevant rose.config_tree.ConfigTree object with the full - configuration. + The relevant metomi.rose.config_tree.ConfigTree object with the + full configuration. item: The current configuration item to process. orig_keys: The keys for locating the originating setting in conf_tree in a @@ -108,7 +108,8 @@ def __init__(self, event_handler=None, popen=None, fs_util=None): if fs_util is None: fs_util = FileSystemUtil(event_handler) self.fs_util = fs_util - path = os.path.dirname(os.path.dirname(sys.modules["rose"].__file__)) + path = os.path.dirname( + os.path.dirname(sys.modules["metomi.rose"].__file__)) SchemeHandlersManager.__init__( self, [path], "rose.config_processors", ["process"]) diff --git a/lib/python/rose/config_processors/__init__.py b/metomi/rose/config_processors/__init__.py similarity index 100% rename from lib/python/rose/config_processors/__init__.py rename to metomi/rose/config_processors/__init__.py diff --git a/lib/python/rose/config_processors/empy.py b/metomi/rose/config_processors/empy.py similarity index 88% rename from lib/python/rose/config_processors/empy.py rename to metomi/rose/config_processors/empy.py index c7a982e5e3..55f44856e8 100644 --- a/lib/python/rose/config_processors/empy.py +++ b/metomi/rose/config_processors/empy.py @@ -17,9 +17,11 @@ # You should have received a copy of the GNU General Public License # along with Rose. If not, see . # ----------------------------------------------------------------------------- -"""Process a section in a rose.config.ConfigNode into a EmPy template.""" +"""Process a section in a metomi.rose.config.ConfigNode into a EmPy template. +""" -from rose.config_processors.jinja2 import ConfigProcessorForJinja2 + +from metomi.rose.config_processors.jinja2 import ConfigProcessorForJinja2 class ConfigProcessorForEmPy(ConfigProcessorForJinja2): diff --git a/lib/python/rose/config_processors/env.py b/metomi/rose/config_processors/env.py similarity index 91% rename from lib/python/rose/config_processors/env.py rename to metomi/rose/config_processors/env.py index 340c40c85f..c0e96385d5 100644 --- a/lib/python/rose/config_processors/env.py +++ b/metomi/rose/config_processors/env.py @@ -17,12 +17,13 @@ # You should have received a copy of the GNU General Public License # along with Rose. If not, see . # ----------------------------------------------------------------------------- -"""Process an env section in node of a rose.config_tree.ConfigTree.""" +"""Process an env section in node of a metomi.rose.config_tree.ConfigTree.""" import os -from rose.env import ( +from metomi.rose.env import ( env_export, env_var_process, UnboundEnvironmentVariableError) -from rose.config_processor import ConfigProcessError, ConfigProcessorBase +from metomi.rose.config_processor import ( + ConfigProcessError, ConfigProcessorBase) class ConfigProcessorForEnv(ConfigProcessorBase): diff --git a/lib/python/rose/config_processors/fileinstall.py b/metomi/rose/config_processors/fileinstall.py similarity index 97% rename from lib/python/rose/config_processors/fileinstall.py rename to metomi/rose/config_processors/fileinstall.py index 2525ffda1f..ff31607787 100644 --- a/lib/python/rose/config_processors/fileinstall.py +++ b/metomi/rose/config_processors/fileinstall.py @@ -17,20 +17,22 @@ # You should have received a copy of the GNU General Public License # along with Rose. If not, see . # ---------------------------------------------------------------------------- -"""Process "file:*" sections in node of a rose.config_tree.ConfigTree.""" +"""Process "file:*" sections in node of a metomi.rose.config_tree.ConfigTree. +""" from fnmatch import fnmatch from glob import glob import os -from rose.checksum import ( +from metomi.rose.checksum import ( get_checksum, get_checksum_func, guess_checksum_algorithm) -from rose.config_processor import ConfigProcessError, ConfigProcessorBase -from rose.env import env_var_process, UnboundEnvironmentVariableError -from rose.fs_util import FileSystemUtil -from rose.job_runner import JobManager, JobProxy, JobRunner -from rose.popen import RosePopener -from rose.reporter import Event -from rose.scheme_handler import SchemeHandlersManager +from metomi.rose.config_processor import (ConfigProcessError, + ConfigProcessorBase) +from metomi.rose.env import env_var_process, UnboundEnvironmentVariableError +from metomi.rose.fs_util import FileSystemUtil +from metomi.rose.job_runner import JobManager, JobProxy, JobRunner +from metomi.rose.popen import RosePopener +from metomi.rose.reporter import Event +from metomi.rose.scheme_handler import SchemeHandlersManager import shlex from shutil import rmtree import sqlite3 @@ -42,7 +44,8 @@ class ConfigProcessorForFile(ConfigProcessorBase): - """Processor for [file:*] in node of a rose.config_tree.ConfigTree.""" + """Processor for [file:*] in node of a metomi.rose.config_tree.ConfigTree. + """ SCHEME = "file" diff --git a/lib/python/rose/config_processors/jinja2.py b/metomi/rose/config_processors/jinja2.py similarity index 92% rename from lib/python/rose/config_processors/jinja2.py rename to metomi/rose/config_processors/jinja2.py index 506bbc18ce..6a9dd1e780 100644 --- a/lib/python/rose/config_processors/jinja2.py +++ b/metomi/rose/config_processors/jinja2.py @@ -17,12 +17,14 @@ # You should have received a copy of the GNU General Public License # along with Rose. If not, see . # ----------------------------------------------------------------------------- -"""Process a section in a rose.config.ConfigNode into a Jinja2 template.""" +"""Process a section in a metomi.rose.config.ConfigNode into a Jinja2 template. +""" import filecmp -from rose.config_processor import ConfigProcessError, ConfigProcessorBase -from rose.env import env_var_process, UnboundEnvironmentVariableError -from rose.fs_util import FileSystemEvent +from metomi.rose.config_processor import ( + ConfigProcessError, ConfigProcessorBase) +from metomi.rose.env import env_var_process, UnboundEnvironmentVariableError +from metomi.rose.fs_util import FileSystemEvent import os from tempfile import NamedTemporaryFile @@ -44,8 +46,8 @@ def process(self, conf_tree, item, orig_keys=None, orig_value=None, Arguments: conf_tree: - The relevant rose.config_tree.ConfigTree object with the full - configuration. + The relevant metomi.rose.config_tree.ConfigTree object with the + full configuration. item: The current configuration item to process. orig_keys: The keys for locating the originating setting in conf_tree in a diff --git a/lib/python/rose/config_tree.py b/metomi/rose/config_tree.py similarity index 97% rename from lib/python/rose/config_tree.py rename to metomi/rose/config_tree.py index fe174ea235..f433070fdc 100644 --- a/lib/python/rose/config_tree.py +++ b/metomi/rose/config_tree.py @@ -21,9 +21,13 @@ """Rose configuration directory inheritance.""" import os -from rose.c3 import mro -from rose.config import ConfigNode, ConfigLoader +from metomi.rose.c3 import mro +from metomi.rose.config import ConfigNode, ConfigLoader import shlex +from metomi.rose.config import ConfigDumper +from io import StringIO +from shutil import rmtree +from tempfile import mkdtemp class BadOptionalConfigurationKeysError(Exception): @@ -92,8 +96,8 @@ def load(self, conf_dir, conf_name, conf_dir_paths=None, opt_keys=None, conf_dir_paths -- A list of directories to locate relative paths to configurations. opt_keys -- Optional configuration keys. - conf_node -- A rose.config.ConfigNode to extend, or None to use a - fresh one. + conf_node -- A metomi.rose.config.ConfigNode to extend, or None to use + a fresh one. no_ignore -- If True, skip loading ignored config settings. defines -- A list of [SECTION]KEY=VALUE overrides. @@ -486,8 +490,4 @@ def run(self): if __name__ == "__main__": # These modules are only required for running the self tests. - from rose.config import ConfigDumper - from io import StringIO - from shutil import rmtree - from tempfile import mkdtemp _Test().run() diff --git a/lib/python/rose/date.py b/metomi/rose/date.py similarity index 99% rename from lib/python/rose/date.py rename to metomi/rose/date.py index 9bd6bbd172..caf8cd18e3 100644 --- a/lib/python/rose/date.py +++ b/metomi/rose/date.py @@ -25,9 +25,9 @@ from isodatetime.parsers import TimePointParser, DurationParser import os import re -from rose.env import UnboundEnvironmentVariableError -from rose.opt_parse import RoseOptionParser -from rose.reporter import Reporter +from metomi.rose.env import UnboundEnvironmentVariableError +from metomi.rose.opt_parse import RoseOptionParser +from metomi.rose.reporter import Reporter import sys diff --git a/lib/python/rose/env.py b/metomi/rose/env.py similarity index 99% rename from lib/python/rose/env.py rename to metomi/rose/env.py index 8aec217583..6b982d1cf9 100644 --- a/lib/python/rose/env.py +++ b/metomi/rose/env.py @@ -26,7 +26,7 @@ import os import re -from rose.reporter import Event +from metomi.rose.reporter import Event # _RE_DEFAULT = re.compile(r""" diff --git a/lib/python/rose/env_cat.py b/metomi/rose/env_cat.py similarity index 94% rename from lib/python/rose/env_cat.py rename to metomi/rose/env_cat.py index 31fd34210b..f55e7b1cf5 100644 --- a/lib/python/rose/env_cat.py +++ b/metomi/rose/env_cat.py @@ -20,8 +20,8 @@ """Implements "rose env-cat".""" -from rose.env import env_var_process, UnboundEnvironmentVariableError -from rose.opt_parse import RoseOptionParser +from metomi.rose.env import env_var_process, UnboundEnvironmentVariableError +from metomi.rose.opt_parse import RoseOptionParser import sys diff --git a/etc/rose-meta/fcm_make/HEAD/rose-meta.conf b/metomi/rose/etc/fcm_make/HEAD/rose-meta.conf similarity index 100% rename from etc/rose-meta/fcm_make/HEAD/rose-meta.conf rename to metomi/rose/etc/fcm_make/HEAD/rose-meta.conf diff --git a/etc/rose-meta/rose-all/etc/images/icon.png b/metomi/rose/etc/rose-all/etc/images/icon.png similarity index 100% rename from etc/rose-meta/rose-all/etc/images/icon.png rename to metomi/rose/etc/rose-all/etc/images/icon.png diff --git a/etc/rose-meta/rose-all/rose-meta.conf b/metomi/rose/etc/rose-all/rose-meta.conf similarity index 100% rename from etc/rose-meta/rose-all/rose-meta.conf rename to metomi/rose/etc/rose-all/rose-meta.conf diff --git a/etc/rose-meta/rose-app-conf/rose-meta.conf b/metomi/rose/etc/rose-app-conf/rose-meta.conf similarity index 100% rename from etc/rose-meta/rose-app-conf/rose-meta.conf rename to metomi/rose/etc/rose-app-conf/rose-meta.conf diff --git a/etc/rose-meta/rose-demo-baked-alaska-icecream/HEAD/rose-meta.conf b/metomi/rose/etc/rose-demo-baked-alaska-icecream/HEAD/rose-meta.conf similarity index 100% rename from etc/rose-meta/rose-demo-baked-alaska-icecream/HEAD/rose-meta.conf rename to metomi/rose/etc/rose-demo-baked-alaska-icecream/HEAD/rose-meta.conf diff --git a/etc/rose-meta/rose-demo-baked-alaska-icecream/vn1.0/rose-meta.conf b/metomi/rose/etc/rose-demo-baked-alaska-icecream/vn1.0/rose-meta.conf similarity index 100% rename from etc/rose-meta/rose-demo-baked-alaska-icecream/vn1.0/rose-meta.conf rename to metomi/rose/etc/rose-demo-baked-alaska-icecream/vn1.0/rose-meta.conf diff --git a/etc/rose-meta/rose-demo-baked-alaska-icecream/vn2.0/rose-meta.conf b/metomi/rose/etc/rose-demo-baked-alaska-icecream/vn2.0/rose-meta.conf similarity index 100% rename from etc/rose-meta/rose-demo-baked-alaska-icecream/vn2.0/rose-meta.conf rename to metomi/rose/etc/rose-demo-baked-alaska-icecream/vn2.0/rose-meta.conf diff --git a/etc/rose-meta/rose-demo-baked-alaska-sponge/HEAD/rose-meta.conf b/metomi/rose/etc/rose-demo-baked-alaska-sponge/HEAD/rose-meta.conf similarity index 100% rename from etc/rose-meta/rose-demo-baked-alaska-sponge/HEAD/rose-meta.conf rename to metomi/rose/etc/rose-demo-baked-alaska-sponge/HEAD/rose-meta.conf diff --git a/lib/python/rose/apps/ana_builtin/__init__.py b/metomi/rose/etc/rose-demo-baked-alaska-sponge/vn1.0/lib/python/macros/__init__.py similarity index 100% rename from lib/python/rose/apps/ana_builtin/__init__.py rename to metomi/rose/etc/rose-demo-baked-alaska-sponge/vn1.0/lib/python/macros/__init__.py diff --git a/etc/rose-meta/rose-demo-baked-alaska-sponge/vn1.0/lib/python/macros/desoggy.py b/metomi/rose/etc/rose-demo-baked-alaska-sponge/vn1.0/lib/python/macros/desoggy.py similarity index 95% rename from etc/rose-meta/rose-demo-baked-alaska-sponge/vn1.0/lib/python/macros/desoggy.py rename to metomi/rose/etc/rose-demo-baked-alaska-sponge/vn1.0/lib/python/macros/desoggy.py index 898df3cbbf..9b77e214e8 100644 --- a/etc/rose-meta/rose-demo-baked-alaska-sponge/vn1.0/lib/python/macros/desoggy.py +++ b/metomi/rose/etc/rose-demo-baked-alaska-sponge/vn1.0/lib/python/macros/desoggy.py @@ -26,10 +26,10 @@ import re import subprocess -import rose.macro +import metomi.rose.macro -class SpongeDeSoggifier(rose.macro.MacroBase): +class SpongeDeSoggifier(metomi.rose.macro.MacroBase): """De-soggifies the sponge.""" diff --git a/etc/rose-meta/rose-demo-baked-alaska-sponge/vn1.0/rose-meta.conf b/metomi/rose/etc/rose-demo-baked-alaska-sponge/vn1.0/rose-meta.conf similarity index 100% rename from etc/rose-meta/rose-demo-baked-alaska-sponge/vn1.0/rose-meta.conf rename to metomi/rose/etc/rose-demo-baked-alaska-sponge/vn1.0/rose-meta.conf diff --git a/etc/rose-meta/rose-demo-baked-alaska-sponge/vn2.0/rose-meta.conf b/metomi/rose/etc/rose-demo-baked-alaska-sponge/vn2.0/rose-meta.conf similarity index 100% rename from etc/rose-meta/rose-demo-baked-alaska-sponge/vn2.0/rose-meta.conf rename to metomi/rose/etc/rose-demo-baked-alaska-sponge/vn2.0/rose-meta.conf diff --git a/etc/rose-meta/rose-demo-baked-alaska/HEAD/rose-meta.conf b/metomi/rose/etc/rose-demo-baked-alaska/HEAD/rose-meta.conf similarity index 100% rename from etc/rose-meta/rose-demo-baked-alaska/HEAD/rose-meta.conf rename to metomi/rose/etc/rose-demo-baked-alaska/HEAD/rose-meta.conf diff --git a/etc/rose-meta/rose-demo-baked-alaska/vn1.0/rose-meta.conf b/metomi/rose/etc/rose-demo-baked-alaska/vn1.0/rose-meta.conf similarity index 100% rename from etc/rose-meta/rose-demo-baked-alaska/vn1.0/rose-meta.conf rename to metomi/rose/etc/rose-demo-baked-alaska/vn1.0/rose-meta.conf diff --git a/etc/rose-meta/rose-demo-baked-alaska/vn2.0/rose-meta.conf b/metomi/rose/etc/rose-demo-baked-alaska/vn2.0/rose-meta.conf similarity index 100% rename from etc/rose-meta/rose-demo-baked-alaska/vn2.0/rose-meta.conf rename to metomi/rose/etc/rose-demo-baked-alaska/vn2.0/rose-meta.conf diff --git a/etc/rose-meta/rose-demo-upgrade-null/0.1/rose-meta.conf b/metomi/rose/etc/rose-demo-upgrade-null/0.1/rose-meta.conf similarity index 100% rename from etc/rose-meta/rose-demo-upgrade-null/0.1/rose-meta.conf rename to metomi/rose/etc/rose-demo-upgrade-null/0.1/rose-meta.conf diff --git a/etc/rose-meta/rose-demo-upgrade-null/0.2/rose-meta.conf b/metomi/rose/etc/rose-demo-upgrade-null/0.2/rose-meta.conf similarity index 100% rename from etc/rose-meta/rose-demo-upgrade-null/0.2/rose-meta.conf rename to metomi/rose/etc/rose-demo-upgrade-null/0.2/rose-meta.conf diff --git a/etc/rose-meta/rose-demo-upgrade-null/versions.py b/metomi/rose/etc/rose-demo-upgrade-null/versions.py similarity index 100% rename from etc/rose-meta/rose-demo-upgrade-null/versions.py rename to metomi/rose/etc/rose-demo-upgrade-null/versions.py diff --git a/etc/rose-meta/rose-demo-upgrade/HEAD/rose-meta.conf b/metomi/rose/etc/rose-demo-upgrade/HEAD/rose-meta.conf similarity index 100% rename from etc/rose-meta/rose-demo-upgrade/HEAD/rose-meta.conf rename to metomi/rose/etc/rose-demo-upgrade/HEAD/rose-meta.conf diff --git a/etc/rose-meta/rose-demo-upgrade/etc/garden0.4/rose-macro-add.conf b/metomi/rose/etc/rose-demo-upgrade/etc/garden0.4/rose-macro-add.conf similarity index 100% rename from etc/rose-meta/rose-demo-upgrade/etc/garden0.4/rose-macro-add.conf rename to metomi/rose/etc/rose-demo-upgrade/etc/garden0.4/rose-macro-add.conf diff --git a/etc/rose-meta/rose-demo-upgrade/garden0.1/rose-meta.conf b/metomi/rose/etc/rose-demo-upgrade/garden0.1/rose-meta.conf similarity index 100% rename from etc/rose-meta/rose-demo-upgrade/garden0.1/rose-meta.conf rename to metomi/rose/etc/rose-demo-upgrade/garden0.1/rose-meta.conf diff --git a/etc/rose-meta/rose-demo-upgrade/garden0.2/rose-meta.conf b/metomi/rose/etc/rose-demo-upgrade/garden0.2/rose-meta.conf similarity index 100% rename from etc/rose-meta/rose-demo-upgrade/garden0.2/rose-meta.conf rename to metomi/rose/etc/rose-demo-upgrade/garden0.2/rose-meta.conf diff --git a/etc/rose-meta/rose-demo-upgrade/garden0.3/rose-meta.conf b/metomi/rose/etc/rose-demo-upgrade/garden0.3/rose-meta.conf similarity index 100% rename from etc/rose-meta/rose-demo-upgrade/garden0.3/rose-meta.conf rename to metomi/rose/etc/rose-demo-upgrade/garden0.3/rose-meta.conf diff --git a/etc/rose-meta/rose-demo-upgrade/garden0.4/rose-meta.conf b/metomi/rose/etc/rose-demo-upgrade/garden0.4/rose-meta.conf similarity index 100% rename from etc/rose-meta/rose-demo-upgrade/garden0.4/rose-meta.conf rename to metomi/rose/etc/rose-demo-upgrade/garden0.4/rose-meta.conf diff --git a/etc/rose-meta/rose-demo-upgrade/garden0.9/rose-meta.conf b/metomi/rose/etc/rose-demo-upgrade/garden0.9/rose-meta.conf similarity index 100% rename from etc/rose-meta/rose-demo-upgrade/garden0.9/rose-meta.conf rename to metomi/rose/etc/rose-demo-upgrade/garden0.9/rose-meta.conf diff --git a/etc/rose-meta/rose-demo-upgrade/versions.py b/metomi/rose/etc/rose-demo-upgrade/versions.py similarity index 100% rename from etc/rose-meta/rose-demo-upgrade/versions.py rename to metomi/rose/etc/rose-demo-upgrade/versions.py diff --git a/metomi/rose/etc/rose-meta/fcm_make/HEAD/rose-meta.conf b/metomi/rose/etc/rose-meta/fcm_make/HEAD/rose-meta.conf new file mode 100644 index 0000000000..acb0cae1d9 --- /dev/null +++ b/metomi/rose/etc/rose-meta/fcm_make/HEAD/rose-meta.conf @@ -0,0 +1,47 @@ +[=mode] +values=fcm_make + +[=args] +description=Extra options or arguments to pass to "fcm make" + +[=dest-orig] +description=Specify the path to the destination of the original make. + =(default=share/$ROSE_TASK_NAME) + +[=dest-cont] +description=Specify the path to the destination of the continuation make. + =(default is the same as "dest-orig") + +[=fast-dest-root-orig] +description=Specify the path to an existing location that can be used as a fast + =working directory for the original make. + +[=fast-dest-root-cont] +description=Specify the path to an existing location that can be used as a fast + =working directory for the continuation make. + +[=make-name-orig] +description=Specify the context name of the original make. + +[=make-name-cont] +description=Specify the context name of the continuation make. + +[=mirror-step] +description=Name of the mirror step (default="mirror") + =Specify an empty string to switch off mirroring + +[=opt.jobs] +description=Number of processes "fcm make" can use in parallel +range=1: +type=integer + +[=orig-cont-map] +description=This setting allows you to override the default fcm_make:fcm_make2 + =mapping between the names of the original and the continuation + =tasks in the suite. +pattern=[^:]*:[^:]* + +[=use-pwd] +description=Use current working directory instead of + =$ROSE_SUITE_DIR/share/$ROSE_TASK_NAME as the working directory? +type=boolean diff --git a/metomi/rose/etc/rose-meta/rose-all/etc/images/icon.png b/metomi/rose/etc/rose-meta/rose-all/etc/images/icon.png new file mode 100644 index 0000000000..905551bcbb Binary files /dev/null and b/metomi/rose/etc/rose-meta/rose-all/etc/images/icon.png differ diff --git a/metomi/rose/etc/rose-meta/rose-all/rose-meta.conf b/metomi/rose/etc/rose-meta/rose-all/rose-meta.conf new file mode 100644 index 0000000000..e69de29bb2 diff --git a/metomi/rose/etc/rose-meta/rose-app-conf/rose-meta.conf b/metomi/rose/etc/rose-meta/rose-app-conf/rose-meta.conf new file mode 100644 index 0000000000..e1fbc13dea --- /dev/null +++ b/metomi/rose/etc/rose-meta/rose-app-conf/rose-meta.conf @@ -0,0 +1,117 @@ +[=file-install-root] +description=Specify the root directory to install file targets that are + =specified with a relative path. + +[=meta] +description=Default id or type of application, used to find the metadata directory +type=meta + +[=mode] +description=Specify the name of a builtin application + +[=opts] +description=A space-delimited list of optional configuration keys to switch on. +help=A space-delimited list of optional configuration keys to switch on. + = + = This tells the run time program to load the relevant optional + = configurations in the opt/ sub-directory at run time. + +[command] +description=Different command options including default. + +[command=default] +description=Default command to be run, after environment is loaded + =and files output. +title=command default + +[env] +description=Environment variable configuration + +[file] +description=File installation configuration + +# Note: the following 'file:*' syntax is unique, and has no wildcard. +# Wildcard operators in metadata sections are not supported. +[file:*=checksum] +description=Checksum for validation +pattern=^[0-9a-f]{32}$|^$ + +# Note: the following 'file:*' syntax is unique, and has no wildcard. +# Wildcard operators in metadata sections are not supported. +[file:*=mode] +description=Operation to perform +values=auto, mkdir, symlink, symlink+ + +# Note: the following 'file:*' syntax is unique, and has no wildcard. +# Wildcard operators in metadata sections are not supported. +[file:*=source] +description=External resource URI +sort-key=00 +type=file + +[poll] +description=Specify prerequisites to poll for before running the actual application. + = + = The tests will be performed once when the application runner starts. + = + = If poll=delays is added, the tests will be performed a number of times + = with delays between them. + +[poll=all-files] +help=A space delimited list of file paths. + = + = This test passes only if all file paths in the list exist. + = + = If a custom test is specified using poll=file-test , it + = will pass only if the test returns a 0 (zero) return code + = for all file paths. + +[poll=any-files] +help=A space delimited list of file paths. + = + = This test passes if any file path in the list exists. + = + = If a custom test is specified using poll=file-test , it + = will pass if the test returns a 0 (zero) return code + = for any file paths. + +[poll=delays] +help=A list of delays between each test re-invocation. + = + = If this is added, the tests will be performed a number of times with the + = appropriate delays between them. + = + = The list is a comma-separated list. The syntax looks like [R*]T[U], + = where U is a unit (s for seconds (default), m for minutes and h for + = hours), T is the number of units. E.g.: + = + =# Default + =delays=0 + =# Poll 1 minute after the runner begins, repeat every minute 10 times + =delays=10*1m + = + =# Poll when runner begins, + =# repeat every 10 seconds 6 times, + =# repeat every minute 60 times, + =# repeat once after 1 hour + =delays=0,6*10s,60*1m,1h + +[poll=file-test] +help=Specify a custom test to be run on each file path in either poll=any-files or + = poll=all-files . + = + = When executing, any {} pattern will be replaced with the relevant file path. + = + = For example, if you were testing for a particular string in a file path: + = + = [poll] + = all-files=file1 file2 + = file-test=test -e {} && grep -q 'hello' {} + +[poll=test] +help=A shell command. + = + = This test passes if the command returns a 0 (zero) return code. + +[ns=namelist] +description=Namelist configuration root page - see sub-pages, if any. diff --git a/metomi/rose/etc/rose-meta/rose-demo-baked-alaska-icecream/HEAD/rose-meta.conf b/metomi/rose/etc/rose-meta/rose-demo-baked-alaska-icecream/HEAD/rose-meta.conf new file mode 100644 index 0000000000..f4c205cd37 --- /dev/null +++ b/metomi/rose/etc/rose-meta/rose-demo-baked-alaska-icecream/HEAD/rose-meta.conf @@ -0,0 +1,4 @@ +[env=ICECREAM_TEMPERATURE] +range=-20:0 +title=Icecream Temperature (celsius) (version HEAD) +type=integer diff --git a/metomi/rose/etc/rose-meta/rose-demo-baked-alaska-icecream/vn1.0/rose-meta.conf b/metomi/rose/etc/rose-meta/rose-demo-baked-alaska-icecream/vn1.0/rose-meta.conf new file mode 100644 index 0000000000..9b0b2e5c99 --- /dev/null +++ b/metomi/rose/etc/rose-meta/rose-demo-baked-alaska-icecream/vn1.0/rose-meta.conf @@ -0,0 +1,8 @@ +[env=ICECREAM_FLAVOUR] +title=Icecream Flavour +values=chocolate,vanilla,strawberry + +[env=ICECREAM_TEMPERATURE] +range=-20:0 +title=Icecream Temperature (celsius) (version 1) +type=integer diff --git a/metomi/rose/etc/rose-meta/rose-demo-baked-alaska-icecream/vn2.0/rose-meta.conf b/metomi/rose/etc/rose-meta/rose-demo-baked-alaska-icecream/vn2.0/rose-meta.conf new file mode 100644 index 0000000000..30648e72fe --- /dev/null +++ b/metomi/rose/etc/rose-meta/rose-demo-baked-alaska-icecream/vn2.0/rose-meta.conf @@ -0,0 +1,4 @@ +[env=ICECREAM_TEMPERATURE] +range=-20:0 +title=Icecream Temperature (celsius) (version 2) +type=integer diff --git a/metomi/rose/etc/rose-meta/rose-demo-baked-alaska-sponge/HEAD/rose-meta.conf b/metomi/rose/etc/rose-meta/rose-demo-baked-alaska-sponge/HEAD/rose-meta.conf new file mode 100644 index 0000000000..1523b66843 --- /dev/null +++ b/metomi/rose/etc/rose-meta/rose-demo-baked-alaska-sponge/HEAD/rose-meta.conf @@ -0,0 +1,4 @@ +[env=SPONGE_DENSITY] +range=0:1 +title=Sponge Density (g cm^-3) (version HEAD) +type=real diff --git a/metomi/rose/etc/rose-meta/rose-demo-baked-alaska-sponge/vn1.0/lib/python/macros/__init__.py b/metomi/rose/etc/rose-meta/rose-demo-baked-alaska-sponge/vn1.0/lib/python/macros/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/rose-version b/metomi/rose/etc/rose-meta/rose-demo-baked-alaska-sponge/vn1.0/lib/python/macros/desoggy.py similarity index 53% rename from rose-version rename to metomi/rose/etc/rose-meta/rose-demo-baked-alaska-sponge/vn1.0/lib/python/macros/desoggy.py index f43721034e..9b77e214e8 100644 --- a/rose-version +++ b/metomi/rose/etc/rose-meta/rose-demo-baked-alaska-sponge/vn1.0/lib/python/macros/desoggy.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # ----------------------------------------------------------------------------- # Copyright (C) 2012-2019 British Crown (Met Office) & Contributors. # @@ -16,4 +17,30 @@ # You should have received a copy of the GNU General Public License # along with Rose. If not, see . # ----------------------------------------------------------------------------- -ROSE_VERSION="2019.01.0" +"""This module contains: + +SpongeDeSoggifier, a rose transform macro. + +""" + +import re +import subprocess + +import metomi.rose.macro + + +class SpongeDeSoggifier(metomi.rose.macro.MacroBase): + + """De-soggifies the sponge.""" + + SOGGY_FIX_TEXT = "de-soggified" + + def transform(self, config, meta_config=None): + """Reduce the density of the sponge.""" + sponge_density = config.get_value(["env", "SPONGE_DENSITY"]) + if sponge_density is not None and float(sponge_density) > 0.5: + # 1 g cm^-3 is pure water, so this is pretty soggy. + config.set(["env", "SPONGE_DENSITY"], "0.3") + self.add_report( + "env", "SPONGE_DENSITY", "0.3", self.SOGGY_FIX_TEXT) + return config, self.reports diff --git a/metomi/rose/etc/rose-meta/rose-demo-baked-alaska-sponge/vn1.0/rose-meta.conf b/metomi/rose/etc/rose-meta/rose-demo-baked-alaska-sponge/vn1.0/rose-meta.conf new file mode 100644 index 0000000000..e23701d71b --- /dev/null +++ b/metomi/rose/etc/rose-meta/rose-demo-baked-alaska-sponge/vn1.0/rose-meta.conf @@ -0,0 +1,4 @@ +[env=SPONGE_DENSITY] +range=0:1 +title=Sponge Density (g cm^-3) (version 1) +type=real diff --git a/metomi/rose/etc/rose-meta/rose-demo-baked-alaska-sponge/vn2.0/rose-meta.conf b/metomi/rose/etc/rose-meta/rose-demo-baked-alaska-sponge/vn2.0/rose-meta.conf new file mode 100644 index 0000000000..655e2e2e04 --- /dev/null +++ b/metomi/rose/etc/rose-meta/rose-demo-baked-alaska-sponge/vn2.0/rose-meta.conf @@ -0,0 +1,4 @@ +[env=SPONGE_DENSITY] +range=0:1 +title=Sponge Density (g cm^-3) (version 2) +type=real diff --git a/metomi/rose/etc/rose-meta/rose-demo-baked-alaska/HEAD/rose-meta.conf b/metomi/rose/etc/rose-meta/rose-demo-baked-alaska/HEAD/rose-meta.conf new file mode 100644 index 0000000000..3c25992a5b --- /dev/null +++ b/metomi/rose/etc/rose-meta/rose-demo-baked-alaska/HEAD/rose-meta.conf @@ -0,0 +1,6 @@ +import=rose-demo-baked-alaska-icecream/HEAD rose-demo-baked-alaska-sponge/ + +[env=MERINGUE_NICENESS] +title=Meringue Niceness (version HEAD) +range=-20:20 +type=integer diff --git a/metomi/rose/etc/rose-meta/rose-demo-baked-alaska/vn1.0/rose-meta.conf b/metomi/rose/etc/rose-meta/rose-demo-baked-alaska/vn1.0/rose-meta.conf new file mode 100644 index 0000000000..15e8508891 --- /dev/null +++ b/metomi/rose/etc/rose-meta/rose-demo-baked-alaska/vn1.0/rose-meta.conf @@ -0,0 +1,13 @@ +import=rose-demo-baked-alaska-icecream/vn1.0 rose-demo-baked-alaska-sponge/vn1.0 + +[ns=env] +description=These settings should all have nice titles, and icecream flavour + should be a fixed variable. + +[env=MERINGUE_NICENESS] +title=Meringue Niceness (version 1) +range=-20:20 +type=integer + +[env=ICECREAM_FLAVOUR] +values=vanilla diff --git a/metomi/rose/etc/rose-meta/rose-demo-baked-alaska/vn2.0/rose-meta.conf b/metomi/rose/etc/rose-meta/rose-demo-baked-alaska/vn2.0/rose-meta.conf new file mode 100644 index 0000000000..baf601563c --- /dev/null +++ b/metomi/rose/etc/rose-meta/rose-demo-baked-alaska/vn2.0/rose-meta.conf @@ -0,0 +1,6 @@ +import=rose-demo-baked-alaska-icecream/vn2.0 rose-demo-baked-alaska-sponge/vn2.0 + +[env=MERINGUE_NICENESS] +title=Meringue Niceness (version 2) +range=-20:20 +type=integer diff --git a/metomi/rose/etc/rose-meta/rose-demo-upgrade-null/0.1/rose-meta.conf b/metomi/rose/etc/rose-meta/rose-demo-upgrade-null/0.1/rose-meta.conf new file mode 100644 index 0000000000..e69de29bb2 diff --git a/metomi/rose/etc/rose-meta/rose-demo-upgrade-null/0.2/rose-meta.conf b/metomi/rose/etc/rose-meta/rose-demo-upgrade-null/0.2/rose-meta.conf new file mode 100644 index 0000000000..e69de29bb2 diff --git a/metomi/rose/etc/rose-meta/rose-demo-upgrade-null/versions.py b/metomi/rose/etc/rose-meta/rose-demo-upgrade-null/versions.py new file mode 100644 index 0000000000..24fda3cc83 --- /dev/null +++ b/metomi/rose/etc/rose-meta/rose-demo-upgrade-null/versions.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------- +# Copyright (C) 2012-2019 British Crown (Met Office) & Contributors. +# ----------------------------------------------------------------------------- +"""Module containing test upgrade macros""" + +import rose.upgrade + + +class UpgradeNull01(rose.upgrade.MacroUpgrade): + + """Upgrade nothing...""" + + BEFORE_TAG = "0.1" + AFTER_TAG = "0.2" + + def upgrade(self, config, meta_config=None): + self.add_report(None, None, None, "nothing...", is_warning=True) + self.add_report("made", "up", None, "made up option", is_warning=True) + return config, self.reports diff --git a/metomi/rose/etc/rose-meta/rose-demo-upgrade/HEAD/rose-meta.conf b/metomi/rose/etc/rose-meta/rose-demo-upgrade/HEAD/rose-meta.conf new file mode 100644 index 0000000000..d7f2196a24 --- /dev/null +++ b/metomi/rose/etc/rose-meta/rose-demo-upgrade/HEAD/rose-meta.conf @@ -0,0 +1,35 @@ +[env] +title=Garden Environment + +[env=AXE] +help=See namelist:trees=mighty_tree +title=Axe for cutting down the tree +values=herring + +[env=FOREST] +type=boolean + +[namelist:features] +ns=features +title=Garden Features + +[namelist:features=rose_bushes] +title=Rose Bushes +type=integer + +[namelist:features=shrubberies] +range=0: +title=Shrubberies +type=integer + +[namelist:features=shrubbery_laurels] +title=Shrubbery laurels +type=character + +[namelist:trees] +ns=trees +title=Garden Trees + +[namelist:trees=mighty_tree] +title=The mightiest tree in the forest. +values=1 diff --git a/metomi/rose/etc/rose-meta/rose-demo-upgrade/etc/garden0.4/rose-macro-add.conf b/metomi/rose/etc/rose-meta/rose-demo-upgrade/etc/garden0.4/rose-macro-add.conf new file mode 100644 index 0000000000..ce13857195 --- /dev/null +++ b/metomi/rose/etc/rose-meta/rose-demo-upgrade/etc/garden0.4/rose-macro-add.conf @@ -0,0 +1,3 @@ +[namelist:features] +two_level_effect=.true. +little_path_running_down_the_middle=.true. diff --git a/metomi/rose/etc/rose-meta/rose-demo-upgrade/garden0.1/rose-meta.conf b/metomi/rose/etc/rose-meta/rose-demo-upgrade/garden0.1/rose-meta.conf new file mode 100644 index 0000000000..aa6b1ae6b3 --- /dev/null +++ b/metomi/rose/etc/rose-meta/rose-demo-upgrade/garden0.1/rose-meta.conf @@ -0,0 +1,14 @@ +[env] +title=Garden Environment + +[env=FOREST] +trigger=namelist:features: true; +type=boolean + +[namelist:features] +ns=features +title=Garden Features + +[namelist:features=rose_bushes] +title=Rose Bushes +type=integer diff --git a/metomi/rose/etc/rose-meta/rose-demo-upgrade/garden0.2/rose-meta.conf b/metomi/rose/etc/rose-meta/rose-demo-upgrade/garden0.2/rose-meta.conf new file mode 100644 index 0000000000..7a1abe023e --- /dev/null +++ b/metomi/rose/etc/rose-meta/rose-demo-upgrade/garden0.2/rose-meta.conf @@ -0,0 +1,19 @@ +[env] +title=Garden Environment + +[env=FOREST] +trigger=namelist:features: true; +type=boolean + +[namelist:features] +ns=features +title=Garden Features + +[namelist:features=rose_bushes] +title=Rose Bushes +type=integer + +[namelist:features=shrubberies] +range=1: +title=Shrubberies +type=integer diff --git a/metomi/rose/etc/rose-meta/rose-demo-upgrade/garden0.3/rose-meta.conf b/metomi/rose/etc/rose-meta/rose-demo-upgrade/garden0.3/rose-meta.conf new file mode 100644 index 0000000000..6aa6b9c1ad --- /dev/null +++ b/metomi/rose/etc/rose-meta/rose-demo-upgrade/garden0.3/rose-meta.conf @@ -0,0 +1,22 @@ +[env] +title=Garden Environment + +[env=FOREST] +type=boolean + +[namelist:features] +ns=features +title=Garden Features + +[namelist:features=rose_bushes] +title=Rose Bushes +type=integer + +[namelist:features=shrubberies] +range=1: +title=Shrubberies +type=integer + +[namelist:features=shrubbery_laurels] +title=Shrubbery laurels +type=character diff --git a/metomi/rose/etc/rose-meta/rose-demo-upgrade/garden0.4/rose-meta.conf b/metomi/rose/etc/rose-meta/rose-demo-upgrade/garden0.4/rose-meta.conf new file mode 100644 index 0000000000..5fb46fa8b9 --- /dev/null +++ b/metomi/rose/etc/rose-meta/rose-demo-upgrade/garden0.4/rose-meta.conf @@ -0,0 +1,23 @@ +[env] +title=Garden Environment + +[env=FOREST] +trigger=namelist:features: true; +type=boolean + +[namelist:features] +ns=features +title=Garden Features + +[namelist:features=rose_bushes] +title=Rose Bushes +type=integer + +[namelist:features=shrubberies] +range=2: +title=Shrubberies +type=integer + +[namelist:features=shrubbery_laurels] +title=Shrubbery laurels +type=character diff --git a/metomi/rose/etc/rose-meta/rose-demo-upgrade/garden0.9/rose-meta.conf b/metomi/rose/etc/rose-meta/rose-demo-upgrade/garden0.9/rose-meta.conf new file mode 100644 index 0000000000..1842f703d8 --- /dev/null +++ b/metomi/rose/etc/rose-meta/rose-demo-upgrade/garden0.9/rose-meta.conf @@ -0,0 +1,37 @@ +[env] +title=Garden Environment + +[env=AXE] +help=See namelist:trees=mighty_tree +title=Axe for cutting down the tree +values=herring + +[env=FOREST] +trigger=namelist:features: true; + namelist:trees: true; +type=boolean + +[namelist:features] +ns=features +title=Garden Features + +[namelist:features=rose_bushes] +title=Rose Bushes +type=integer + +[namelist:features=shrubberies] +range=0: +title=Shrubberies +type=integer + +[namelist:features=shrubbery_laurels] +title=Shrubbery laurels +type=character + +[namelist:trees] +ns=trees +title=Garden Trees + +[namelist:trees=mighty_tree] +title=The mightiest tree in the forest. +values=1 diff --git a/metomi/rose/etc/rose-meta/rose-demo-upgrade/versions.py b/metomi/rose/etc/rose-meta/rose-demo-upgrade/versions.py new file mode 100644 index 0000000000..0a130b5aa3 --- /dev/null +++ b/metomi/rose/etc/rose-meta/rose-demo-upgrade/versions.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------- +# Copyright (C) 2012-2019 British Crown (Met Office) & Contributors. +# ----------------------------------------------------------------------------- +"""Module containing example macros for using rose app-upgrade. + +Quotes are copyright Python (Monty) Pictures Ltd, Freeway Cam (UK) Ltd. + +""" + +import rose.upgrade + + +class UpgradeGarden01(rose.upgrade.MacroUpgrade): + + """'We want... a shrubbery!'""" + + BEFORE_TAG = "garden0.1" + AFTER_TAG = "garden0.2" + + def downgrade(self, config, meta_config=None): + self.remove_setting(config, ["namelist:features", "shrubberies"]) + return config, self.reports + + def upgrade(self, config, meta_config=None): + self.add_setting(config, ["namelist:features", "shrubberies"], "1") + return config, self.reports + + +class UpgradeGarden02(rose.upgrade.MacroUpgrade): + + """'...there is one small problem...'""" + + BEFORE_TAG = "garden0.2" + AFTER_TAG = "garden0.3" + + def downgrade(self, config, meta_config=None): + self.remove_setting(config, ["namelist:features", "shrubbery_laurels"]) + return config, self.reports + + def upgrade(self, config, meta_config=None): + self.add_setting(config, ["namelist:features", "shrubbery_laurels"], + "'particularly nice'") + shrub_num = self.get_setting_value( + config, ["namelist:features", "shrubberies"]) + if shrub_num in ["0", "1"]: + self.add_report("namelist:features", "shrubberies", shrub_num, + info="More than one shrubbery is desirable", + is_warning=True) + return config, self.reports + + +class UpgradeGarden03(rose.upgrade.MacroUpgrade): + + """'You must find... another shrubbery!'""" + + BEFORE_TAG = "garden0.3" + AFTER_TAG = "garden0.4" + + def downgrade(self, config, meta_config=None): + if self._get_shrub_num(config) == 2: + self.change_setting_value( + config, ["namelist:features", "shrubberies"], "1") + return config, self.reports + + def upgrade(self, config, meta_config=None): + if self._get_shrub_num(config) == 1: + self.change_setting_value( + config, ["namelist:features", "shrubberies"], "2", + info="Fetched another shrubbery") + return config, self.reports + + def _get_shrub_num(self, config): + shrub_num = self.get_setting_value( + config, ["namelist:features", "shrubberies"]) + try: + shrub_num = float(shrub_num) + except (TypeError, ValueError): + return None + return shrub_num + + +class UpgradeGarden041(rose.upgrade.MacroUpgrade): + + """'...the two-level effect with a little path running down the middle'""" + + BEFORE_TAG = "garden0.4" + AFTER_TAG = "garden0.4.1" + + def downgrade(self, config, meta_config=None): + self.act_from_files(config, downgrade=True) + return config, self.reports + + def upgrade(self, config, meta_config=None): + self.act_from_files(config, downgrade=False) + return config, self.reports + + +class UpgradeGarden09(rose.upgrade.MacroUpgrade): + + """'cut down the mightiest tree in the forest... with... a herring!'""" + + BEFORE_TAG = "garden0.4.1" + AFTER_TAG = "garden0.9" + + def downgrade(self, config, meta_config=None): + self.remove_setting(config, ["env", "AXE"]) + self.remove_setting(config, ["namelist:trees"]) + return config, self.reports + + def upgrade(self, config, meta_config=None): + self.add_setting(config, ["namelist:trees", "mighty_tree"], "1") + self.add_setting(config, ["env", "AXE"], "herring") + return config, self.reports diff --git a/etc/rose-meta/rose-suite-conf/rose-meta.conf b/metomi/rose/etc/rose-meta/rose-suite-conf/rose-meta.conf similarity index 100% rename from etc/rose-meta/rose-suite-conf/rose-meta.conf rename to metomi/rose/etc/rose-meta/rose-suite-conf/rose-meta.conf diff --git a/etc/rose-meta/rose-suite-info/rose-meta.conf b/metomi/rose/etc/rose-meta/rose-suite-info/rose-meta.conf similarity index 100% rename from etc/rose-meta/rose-suite-info/rose-meta.conf rename to metomi/rose/etc/rose-meta/rose-suite-info/rose-meta.conf diff --git a/etc/rose-meta/rose_bunch/HEAD/rose-meta.conf b/metomi/rose/etc/rose-meta/rose_bunch/HEAD/rose-meta.conf similarity index 100% rename from etc/rose-meta/rose_bunch/HEAD/rose-meta.conf rename to metomi/rose/etc/rose-meta/rose_bunch/HEAD/rose-meta.conf diff --git a/etc/rose-meta/rose_prune/HEAD/rose-meta.conf b/metomi/rose/etc/rose-meta/rose_prune/HEAD/rose-meta.conf similarity index 100% rename from etc/rose-meta/rose_prune/HEAD/rose-meta.conf rename to metomi/rose/etc/rose-meta/rose_prune/HEAD/rose-meta.conf diff --git a/metomi/rose/etc/rose-suite-conf/rose-meta.conf b/metomi/rose/etc/rose-suite-conf/rose-meta.conf new file mode 100644 index 0000000000..dc580e7a0f --- /dev/null +++ b/metomi/rose/etc/rose-suite-conf/rose-meta.conf @@ -0,0 +1,72 @@ +[=file-install-root] +description=Specify the root directory to install file targets that are + =specified with a relative path. + +[=meta] +description=Default id or type of application, used to find the metadata directory +type=meta + +[=opts] +description=A space-delimited list of optional configuration keys to switch on. +help=A space-delimited list of optional configuration keys to switch on. + = + = This tells the run time program to load the relevant optional + = configurations in the opt/ sub-directory at run time. + +[=root-dir] +description=Specify the suite's directory location on particular hosts. +help=A new line delimited list of PATTERN=DIR pairs. + = + = The PATTERN should be a glob-like pattern for matching a host name. + = The DIR should be the root directory where the suite's directory + = should be created. + +[=root-dir{share}] +description=Specify the suite's share/ directory location on particular hosts. +help=A new line delimited list of PATTERN=DIR pairs. + = + = The PATTERN should be a glob-like pattern for matching a host name. + = The DIR should be the root directory where the suite's share directory + = should be created. + +[=root-dir{share/cycle}] +description=Specify the suite's share/cycle/ directory location on particular hosts. +help=A new line delimited list of PATTERN=DIR pairs. + = + = The PATTERN should be a glob-like pattern for matching a host name. + = The DIR should be the root directory where the suite's share/cycle/ + = directory should be created. + +[=root-dir{work}] +description=Specify the suite's work/ directory location on particular hosts. +help=A new line delimited list of PATTERN=DIR pairs. + = + = The PATTERN should be a glob-like pattern for matching a host name. + = The DIR should be the root directory where the suite's work directory + = for tasks should be created. + +[env] +description=Environment variable configuration + +# Note: the following 'file:*' syntax is unique, and has no wildcard. +# Wildcard operators in metadata sections are not supported. +[file:*=checksum] +description=Checksum for external resource (optional) +pattern=^[0-9a-f]{32}$|^$ + +# Note: the following 'file:*' syntax is unique, and has no wildcard. +# Wildcard operators in metadata sections are not supported. +[file:*=mode] +description=Mode to apply when running - e.g copy the file +values=auto, mkdir, symlink, symlink+ + +# Note: the following 'file:*' syntax is unique, and has no wildcard. +# Wildcard operators in metadata sections are not supported. +[file:*=source] +description=External resource URI +sort-key=00 +type=file + +[jinja2:suite.rc] +description=Template variables +ns=jinja2 diff --git a/metomi/rose/etc/rose-suite-info/rose-meta.conf b/metomi/rose/etc/rose-suite-info/rose-meta.conf new file mode 100644 index 0000000000..20f546cdd5 --- /dev/null +++ b/metomi/rose/etc/rose-suite-info/rose-meta.conf @@ -0,0 +1,66 @@ +[] +compulsory=true + +[=access-list] +description=A space-separated list of trunk-commit-access users. +help=Specify a space-separated list of users apart from the owner who have + = commit access to the suite trunk. + = + = Entering an asterisk ('*') implies all users have commit access. + = + = Entering nothing (or omitting this option) implies nobody apart from + = the owner has commit access to the trunk. + = + = For example, entering: + = + = jane bob fred + = + = gives trunk commit access to users jane, bob, and fred. Entering: + = + = * + = + = gives trunk commit access to all users. +sort-key=02-users-0 + +[=description] +description=A long description of the suite. +help=A long description of the suite - multi-lines accepted. Inheritance of the suite is included here. +widget[rose-config-edit]=rose.config_editor.valuewidget.text.TextMultilineValueWidget +pattern=^.+(?# Must not be empty) +sort-key=03-type-1 + +[=owner] +compulsory=true +description=Username of the suite owner +help=User ID of the owner of the suite. + = + = The owner has full commit access to the suite. + = Only the owner can pass the suite's ownership to someone else. + = Only the owner can delete the suite. +pattern=^.+(?# Must not be empty) +sort-key=02-users-1 + +[=project] +compulsory=true +description=The project associated with the suite. +help=A relevant project for the suite. + = + = This is used to look for metadata for the rest of the suite discovery + = information. +pattern=^.+(?# Must not be empty) +sort-key=00-type-0 +type=meta + +[=sub-project] +description=Sub-division of =project +help=An optional sub-project name. + = + = For example, if =project was Human Genome, =sub-project might be + = chromosome-6 + +[=title] +compulsory=true +description=A short title for the suite. +help=Title of the suite e.g. Spam Testing +pattern=^.+(?# Must not be empty) +sort-key=01-help-1 diff --git a/metomi/rose/etc/rose_bunch/HEAD/rose-meta.conf b/metomi/rose/etc/rose_bunch/HEAD/rose-meta.conf new file mode 100644 index 0000000000..bfaf496d50 --- /dev/null +++ b/metomi/rose/etc/rose_bunch/HEAD/rose-meta.conf @@ -0,0 +1,74 @@ +[=mode] +values=rose_bunch + +[bunch-args] +description=Define sets of arguments for passing to + =[bunch]command-format here. + +[bunch] +compulsory=true +description=Settings for "rose_bunch" built-in application. +help="rose bunch" built-in application used to run multiple commands from a + =rose task-run invocation. + +[bunch=command-format] +description=A Pythonic printf style format string to construct the commands + =to run. +help=Insert placeholders %(argname)s for substitution of the arguments + =specified under [bunch-args] to the invoked command. The placeholder + =%(instances)s is reserved for inserting an automatically generated index + =for the command invocation when using the "instances" setting. + +[bunch=fail-mode] +description=Control how running of commands is affected by failures +help=Use to control what happens to the running of commands when one of the + =invoked command instances fails. + = + =abort: don't run any more commands + =continue: run all commands + = + =Default=continue +values=abort, continue + +[bunch=incremental] +description=Enable incremental mode. +help=Enable/disable running in incremental mode. In incremental mode succeeded + =commands will not be re-run on subsequent task retries. +type=boolean + +[bunch=argument-mode] +description=Update the list of usable bunch-args values. +help=Update the provided values for each bunch-arg based on values in other + =bunch-args. + = + =Default: do not update the bunch-args values. + =izip: shorten bunch-args to be the same length as the smallest one + =izip_longest: lengthen bunch-args to be the same length as the longest one. + = The new values will be an empty string + =product: create a cartesian product of permutations across all bunch-args + = e.g. bunch-args=[a,b,c], [1,2] => [a,a,b,b,c,c], [1,2,1,2,1,2] + =See https://docs.python.org/2/library/itertools.html for more information + =Default="Default" +type=string +values=Default, izip, izip_longest, product + +[bunch=command-instances] +description=Generate a number of command instances. +help=Specify a number of command instances to create. Values for these are made + =available to the command-format setting via %(instances)s. +range=1: +type=integer + +[bunch=pool-size] +description=Restrict the number of commands to run at once. +help=Integer value to limit the number of command instances that are allowed + =to run at the same time. +range=1: +type=integer + +[bunch=names] +description=Name specific invocations of commands +help=Allows the naming of specific invocations of commands. They will be + =referenced in task logs by this name and output from specific commands + =will be directed to named output files. +type=spaced_list diff --git a/metomi/rose/etc/rose_prune/HEAD/rose-meta.conf b/metomi/rose/etc/rose_prune/HEAD/rose-meta.conf new file mode 100644 index 0000000000..5ecdb88b0a --- /dev/null +++ b/metomi/rose/etc/rose_prune/HEAD/rose-meta.conf @@ -0,0 +1,80 @@ +[=mode] +values=rose_prune + +[prune] +description=Setting for "rose_prune" built-in application. +help="rose prune" built-in application is used to housekeep a cycling suite. + +[prune=archive-logs-at] +description=Archive (tar-gzip) job logs at these cycles (after a re-sync) +help=Archive all job logs at these cycles. Remove remote job logs on success. + = + =A space delimited list of cycles, normally expressed as date/time + =offsets relative to the current cycle, in a format recognised by the + ="--offset=OFFSET" option of "rose date". E.g. "-6h" is 6 hours before the + =current cycle time. +sort-key=02 + +[prune=prune-datac-at] +description=Remove (items in) the ROSE_DATACs of the specified cycles +help=Remove the ROSE_DATACs of the specified cycles, or items in them. + = + =A space delimited list of cycles, normally expressed as date/time + =offsets relative to the current cycle, in a format recognised by the + ="--offset=OFFSET" option of "rose date". E.g. "-6h" is 6 hours before the + =current cycle time. + = + =Each cycle can have an optional argument followed by a colon. The argument + =should be a list of globs for matching items in the directory. If + =specified, only matching items will be pruned. Otherwise, the whole + =directory is pruned. + = + =E.g. The following would remove only items matching "foo*" in the + =ROSE_DATAC 6 hours before the current cycle, only items matching "bar*" + =and items matching "baz*" in the ROSE_DATAC 12 hours before the current + =cycle and all of the ROSE_DATAC 1 day before the current cycle. + = + =prune-datac-at=-6h:foo* -12h:'bar* baz*' -1d +sort-key=04 + +[prune=prune-remote-logs-at] +description=Remove remote job logs at these cycles (after a re-sync) +help=Re-sync remote job logs at these cycles and remove them from remote hosts. + = + =A space delimited list of cycles, normally expressed as date/time + =offsets relative to the current cycle, in a format recognised by the + ="--offset=OFFSET" option of "rose date". E.g. "-6h" is 6 hours before the + =current cycle time. +sort-key=01 + +[prune=prune-server-logs-at] +description=Remove logs on the suite server at these cycles. +help=Removes both log directories and archived logs on the suite server. + = + =A space delimited list of cycles, normally expressed as date/time + =offsets relative to the current cycle, in a format recognised by the + ="--offset=OFFSET" option of "rose date". E.g. "-6h" is 6 hours before the + =current cycle time. +sort-key=05 + +[prune=prune-work-at] +description=Remove (items in) the work directory of the specified cycles +help=Remove the work directories of the specified cycles, or items in them. + = + =A space delimited list of cycles, normally expressed as date/time + =offsets relative to the current cycle, in a format recognised by the + ="--offset=OFFSET" option of "rose date". E.g. "-6h" is 6 hours before the + =current cycle time. + = + =Each cycle can have an optional argument followed by a colon. The argument + =should be a list of globs for matching items in the directory. If + =specified, only matching items will be pruned. Otherwise, the whole + =directory is pruned. + = + =E.g. The following would remove only items matching "foo*" in the work + =directories 6 hours before the current cycle, only items matching "bar*" + =and items matching "baz*" in the work directories 12 hours before the + =current cycle and all of the work directory 1 day before the current cycle. + = + =prune-work-at=-6h:foo* -12h:'bar* baz*' -1d +sort-key=03 diff --git a/lib/python/rose/external.py b/metomi/rose/external.py similarity index 98% rename from lib/python/rose/external.py rename to metomi/rose/external.py index 7e42e1e5b6..fb1ab07c5f 100644 --- a/lib/python/rose/external.py +++ b/metomi/rose/external.py @@ -21,7 +21,7 @@ import sys -from rose.popen import RosePopener +from metomi.rose.popen import RosePopener def _launch(name, event_handler=None, run_fg=False, *args, **kwargs): diff --git a/lib/python/rose/formats/__init__.py b/metomi/rose/formats/__init__.py similarity index 100% rename from lib/python/rose/formats/__init__.py rename to metomi/rose/formats/__init__.py diff --git a/lib/python/rose/formats/namelist.py b/metomi/rose/formats/namelist.py similarity index 100% rename from lib/python/rose/formats/namelist.py rename to metomi/rose/formats/namelist.py diff --git a/lib/python/rose/fs_util.py b/metomi/rose/fs_util.py similarity index 99% rename from lib/python/rose/fs_util.py rename to metomi/rose/fs_util.py index bef3be24c8..3d87fbfb41 100644 --- a/lib/python/rose/fs_util.py +++ b/metomi/rose/fs_util.py @@ -21,7 +21,7 @@ import errno import os -from rose.reporter import Event +from metomi.rose.reporter import Event import shutil diff --git a/lib/python/rose/host_select.py b/metomi/rose/host_select.py similarity index 99% rename from lib/python/rose/host_select.py rename to metomi/rose/host_select.py index a1d9615113..26014e6fc4 100644 --- a/lib/python/rose/host_select.py +++ b/metomi/rose/host_select.py @@ -21,10 +21,10 @@ import os from random import choice, random, shuffle -from rose.opt_parse import RoseOptionParser -from rose.popen import RosePopener -from rose.reporter import Reporter, Event -from rose.resource import ResourceLocator +from metomi.rose.opt_parse import RoseOptionParser +from metomi.rose.popen import RosePopener +from metomi.rose.reporter import Reporter, Event +from metomi.rose.resource import ResourceLocator import shlex import signal from socket import ( diff --git a/lib/python/rose/job_runner.py b/metomi/rose/job_runner.py similarity index 99% rename from lib/python/rose/job_runner.py rename to metomi/rose/job_runner.py index 6dc360d7dd..1874de9075 100644 --- a/lib/python/rose/job_runner.py +++ b/metomi/rose/job_runner.py @@ -21,7 +21,7 @@ import asyncio -from rose.reporter import Event +from metomi.rose.reporter import Event class JobEvent(Event): diff --git a/lib/python/rose/loc_handlers/__init__.py b/metomi/rose/loc_handlers/__init__.py similarity index 100% rename from lib/python/rose/loc_handlers/__init__.py rename to metomi/rose/loc_handlers/__init__.py diff --git a/lib/python/rose/loc_handlers/fs.py b/metomi/rose/loc_handlers/fs.py similarity index 98% rename from lib/python/rose/loc_handlers/fs.py rename to metomi/rose/loc_handlers/fs.py index b705e78a1e..8ea65d980e 100644 --- a/lib/python/rose/loc_handlers/fs.py +++ b/metomi/rose/loc_handlers/fs.py @@ -20,7 +20,7 @@ """A handler of file system locations.""" import errno -from rose.checksum import get_checksum +from metomi.rose.checksum import get_checksum import os diff --git a/lib/python/rose/loc_handlers/namelist.py b/metomi/rose/loc_handlers/namelist.py similarity index 89% rename from lib/python/rose/loc_handlers/namelist.py rename to metomi/rose/loc_handlers/namelist.py index f9af044116..9d774ddc83 100644 --- a/lib/python/rose/loc_handlers/namelist.py +++ b/metomi/rose/loc_handlers/namelist.py @@ -17,14 +17,15 @@ # You should have received a copy of the GNU General Public License # along with Rose. If not, see . # ----------------------------------------------------------------------------- -"""Process namelist: sections in a rose.config.ConfigNode matching a name.""" +"""Process namelist: sections in a metomi.rose.config.ConfigNode matching a +name.""" import re -import rose.config -from rose.config_processor import ConfigProcessError -from rose.env import env_var_process, UnboundEnvironmentVariableError -from rose.reporter import Event +import metomi.rose.config +from metomi.rose.config_processor import ConfigProcessError +from metomi.rose.env import env_var_process, UnboundEnvironmentVariableError +from metomi.rose.reporter import Event from functools import cmp_to_key @@ -70,7 +71,7 @@ async def pull(self, loc, conf_tree): """Write namelist to loc.cache.""" sections = self.parse(loc, conf_tree) if loc.name.endswith("(:)"): - sections.sort(key=cmp_to_key(rose.config.sort_settings)) + sections.sort(key=cmp_to_key(metomi.rose.config.sort_settings)) with open(loc.cache, "wb") as handle: for section in sections: section_value = conf_tree.node.get_value([section]) diff --git a/lib/python/rose/loc_handlers/rsync.py b/metomi/rose/loc_handlers/rsync.py similarity index 99% rename from lib/python/rose/loc_handlers/rsync.py rename to metomi/rose/loc_handlers/rsync.py index 245ac7ebb4..0b53a25e22 100644 --- a/lib/python/rose/loc_handlers/rsync.py +++ b/metomi/rose/loc_handlers/rsync.py @@ -21,7 +21,7 @@ from tempfile import TemporaryFile from time import sleep, time -from rose.popen import RosePopenError +from metomi.rose.popen import RosePopenError class RsyncLocHandler(object): diff --git a/lib/python/rose/loc_handlers/svn.py b/metomi/rose/loc_handlers/svn.py similarity index 100% rename from lib/python/rose/loc_handlers/svn.py rename to metomi/rose/loc_handlers/svn.py diff --git a/lib/python/rose/macro.py b/metomi/rose/macro.py similarity index 86% rename from lib/python/rose/macro.py rename to metomi/rose/macro.py index c0167ae4a9..2756dbb981 100644 --- a/lib/python/rose/macro.py +++ b/metomi/rose/macro.py @@ -21,7 +21,7 @@ .. testsetup:: * import os - from rose.macro import * + from metomi.rose.macro import * def test_cleanup(stuff_to_remove): for item in stuff_to_remove: @@ -50,13 +50,14 @@ def test_cleanup(stuff_to_remove): from functools import cmp_to_key from importlib.machinery import SourceFileLoader -import rose.config -import rose.config_tree -import rose.formats.namelist -from rose.opt_parse import RoseOptionParser -import rose.reporter -import rose.resource -import rose.variable +import metomi.rose.config +import metomi.rose.config_tree +import metomi.rose.formats.namelist +from metomi.rose.opt_parse import RoseOptionParser +import metomi.rose.reporter +import metomi.rose.resource +import metomi.rose.variable +from metomi.rose.config import ConfigNode ALLOWED_MACRO_CLASS_METHODS = ["transform", "validate", "downgrade", "upgrade", @@ -78,8 +79,8 @@ def test_cleanup(stuff_to_remove): ERROR_RETURN_VALUE = "{0}: incorrect return value" ERROR_RETURN_VALUE_STATE = "{0}: node.state: invalid returned value" MACRO_DIRNAME = os.path.join(os.path.join("lib", "python"), - rose.META_DIR_MACRO) -ERROR_OUT_DIR_MULTIPLE_APPS = ("Cannot specify an output dir when running rose" + metomi.rose.META_DIR_MACRO) +ERROR_OUT_DIR_MULTIPLE_APPS = ("Cannot specify an output dir when running" " macro over multiple apps.") MACRO_EXT = ".py" MACRO_OUTPUT_HELP = " # {0}\n" @@ -104,11 +105,11 @@ def test_cleanup(stuff_to_remove): VERBOSE_LIST = "{0} - ({1}) - {2}" -class MacroFinishNothingEvent(rose.reporter.Event): +class MacroFinishNothingEvent(metomi.rose.reporter.Event): """Event reported when there have been no problems or changes.""" - LEVEL = rose.reporter.Event.VV + LEVEL = metomi.rose.reporter.Event.VV def __str__(self): return "Configurations OK" @@ -130,7 +131,7 @@ def __str__(self): return ERROR_MACRO_NOT_FOUND.format(self.args[0]) -class MacroTransformDumpEvent(rose.reporter.Event): +class MacroTransformDumpEvent(metomi.rose.reporter.Event): """Event reported when a transformed configuration is dumped.""" @@ -177,9 +178,9 @@ class MacroBase(object): """Base class for macros for validating or transforming configurations. Synopsis: - >>> import rose.macro + >>> import metomi.rose.macro ... - >>> class SomeValidator(rose.macro.MacroBase): + >>> class SomeValidator(metomi.rose.macro.MacroBase): ... ... '''Important: Add a docstring for your macro like this. ... @@ -240,12 +241,12 @@ def _sorter(self, rep1, rep2): # This logic replicates output of the deprecated Python2 `cmp` # builtin return (rep1.value > rep2.value) - (rep1.value < rep2.value) - return rose.config.sort_settings(id1, id2) + return metomi.rose.config.sort_settings(id1, id2) def _load_meta_config(self, config, meta=None, directory=None, config_type=None): """Return a metadata configuration object.""" - if isinstance(meta, rose.config.ConfigNode): + if isinstance(meta, metomi.rose.config.ConfigNode): return meta return load_meta_config(config, directory, config_type=config_type) @@ -254,8 +255,8 @@ def get_metadata_for_config_id(self, setting_id, meta_config): Args: setting_id (str): The name of the setting to extract metadata for. - meta_config (rose.config.ConfigNode): Config node containing the - metadata to extract from. + meta_config (metomi.rose.config.ConfigNode): Config node containing + the metadata to extract from. Return: dict: A dictionary containing metadata options. @@ -282,7 +283,7 @@ def get_metadata_for_config_id(self, setting_id, meta_config): >>> get_metadata_for_config_id('foo=bar', meta_config) {'values': '1,2,3', 'id': 'foo=bar'} - .. testcleanup:: rose.macro.MacroBase.get_metadata_for_config_id + .. testcleanup:: metomi.rose.macro.MacroBase.get_metadata_for_config_id test_cleanup(['rose-app.conf', 'meta/rose-meta.conf', 'meta']) """ @@ -323,7 +324,7 @@ def pretty_format_config(self, config): """Standardise the keys and values of a config node. Args: - config (rose.config.ConfigNode): The config node to convert. + config (metomi.rose.config.ConfigNode): The config node to convert. """ pretty_format_config(config) @@ -332,13 +333,13 @@ def standard_format_config(self, config): """Standardise any degenerate representations e.g. namelist repeats. Args: - config (rose.config.ConfigNode): The config node to convert. + config (metomi.rose.config.ConfigNode): The config node to convert. """ standard_format_config(config) def add_report(self, *args, **kwargs): - """Add a rose.macro.MacroReport. + """Add a metomi.rose.macro.MacroReport. See :class:`rose.macro.MacroReport` for details of arguments. @@ -366,15 +367,15 @@ class MacroBaseRoseEdit(MacroBase): """This class extends MacroBase to provide a non-ConfigNode API. In the following methods, config_data can be a - rose.config.ConfigNode instance or a dictionary that + metomi.rose.config.ConfigNode instance or a dictionary that looks like this: {"sections": - {"namelist:foo": rose.section.Section instance, - "env": rose.section.Section instance}, + {"namelist:foo": metomi.rose.section.Section instance, + "env": metomi.rose.section.Section instance}, "variables": - {"namelist:foo": [rose.variable.Variable instance, - rose.variable.Variable instance], - "env": [rose.variable.Variable instance]} + {"namelist:foo": [metomi.rose.variable.Variable instance, + metomi.rose.variable.Variable instance], + "env": [metomi.rose.variable.Variable instance]} } This makes it easy to interface with rose edit, which uses the latter data structure internally. @@ -384,7 +385,7 @@ class MacroBaseRoseEdit(MacroBase): def _get_config_sections(self, config_data): """Return all sections within config_data.""" sections = [] - if isinstance(config_data, rose.config.ConfigNode): + if isinstance(config_data, metomi.rose.config.ConfigNode): for key, node in config_data.value.items(): if isinstance(node.value, dict): sections.append(key) @@ -398,7 +399,7 @@ def _get_config_sections(self, config_data): def _get_config_section_options(self, config_data, section): """Return all options within a section in config_data.""" - if isinstance(config_data, rose.config.ConfigNode): + if isinstance(config_data, metomi.rose.config.ConfigNode): names = [] for keylist, _ in config_data.walk([section]): names.append(keylist[-1]) @@ -409,7 +410,7 @@ def _get_config_section_options(self, config_data, section): def _get_config_has_id(self, config_data, id_): """Return whether the config_data contains the id_.""" section, option = self._get_section_option_from_id(id_) - if isinstance(config_data, rose.config.ConfigNode): + if isinstance(config_data, metomi.rose.config.ConfigNode): return (config_data.get([section, option]) is not None) if option is None: return section in config_data["sections"] @@ -419,7 +420,7 @@ def _get_config_has_id(self, config_data, id_): def _get_config_id_state(self, config_data, id_): """Return the ConfigNode.STATE_* that applies to id_ or None.""" section, option = self._get_section_option_from_id(id_) - if isinstance(config_data, rose.config.ConfigNode): + if isinstance(config_data, metomi.rose.config.ConfigNode): node = config_data.get([section, option]) if node is None: return None @@ -436,18 +437,18 @@ def _get_config_id_state(self, config_data, id_): break if ignored_reason is None: return None - if rose.variable.IGNORED_BY_USER in ignored_reason: - return rose.config.ConfigNode.STATE_USER_IGNORED - if rose.variable.IGNORED_BY_SYSTEM in ignored_reason: - return rose.config.ConfigNode.STATE_SYST_IGNORED - return rose.config.ConfigNode.STATE_NORMAL + if metomi.rose.variable.IGNORED_BY_USER in ignored_reason: + return metomi.rose.config.ConfigNode.STATE_USER_IGNORED + if metomi.rose.variable.IGNORED_BY_SYSTEM in ignored_reason: + return metomi.rose.config.ConfigNode.STATE_SYST_IGNORED + return metomi.rose.config.ConfigNode.STATE_NORMAL def _get_config_id_value(self, config_data, id_): """Return a value (if any) for id_ in the config_data.""" section, option = self._get_section_option_from_id(id_) if option is None: return None - if isinstance(config_data, rose.config.ConfigNode): + if isinstance(config_data, metomi.rose.config.ConfigNode): node = config_data.get([section, option]) if node is None: return None @@ -546,13 +547,14 @@ def add_meta_paths(): def add_site_meta_paths(): """Load any metadata paths specified in a user or site configuration.""" - conf = rose.resource.ResourceLocator.default().get_conf() - path = conf.get_value([rose.CONFIG_SECT_TOP, rose.CONFIG_OPT_META_PATH]) + conf = metomi.rose.resource.ResourceLocator.default().get_conf() + path = conf.get_value( + [metomi.rose.CONFIG_SECT_TOP, metomi.rose.CONFIG_OPT_META_PATH]) if path is not None: for path in path.split(os.pathsep): path = os.path.expanduser(os.path.expandvars(path)) sys.path.insert(0, os.path.abspath(path)) - sys.path.append(os.path.join(os.getenv("ROSE_HOME"), "etc/rose-meta")) + sys.path.append(os.path.join(os.getenv("ROSE_LIB"), "etc/rose-meta")) def add_env_meta_paths(): @@ -577,7 +579,7 @@ def add_opt_meta_paths(meta_paths): def get_section_option_from_id(var_id): """Return a configuration section and option from an id.""" - section_option = var_id.split(rose.CONFIG_DELIMITER, 1) + section_option = var_id.split(metomi.rose.CONFIG_DELIMITER, 1) if len(section_option) == 1: return var_id, None return section_option @@ -587,7 +589,7 @@ def get_id_from_section_option(section, option): """Return a variable id from a section and option.""" if option is None: return section - return section + rose.CONFIG_DELIMITER + option + return section + metomi.rose.CONFIG_DELIMITER + option def load_meta_path(config=None, directory=None, is_upgrade=False, @@ -598,19 +600,19 @@ def load_meta_path(config=None, directory=None, is_upgrade=False, config - a rose config, perhaps with a meta= or project= flag directory - the directory of the rose config file is_upgrade - if True, load the path in an upgrade-specific way - locator - a rose.resource.ResourceLocator instance. + locator - a metomi.rose.resource.ResourceLocator instance. Returns the path to(or None) and a warning message (or None). """ if config is None: - config = rose.config.ConfigNode() + config = metomi.rose.config.ConfigNode() if no_warn is None: no_warn = [] warning = None if directory is not None and not is_upgrade: - config_meta_dir = os.path.join(directory, rose.CONFIG_META_DIR) - meta_file = os.path.join(config_meta_dir, rose.META_CONFIG_NAME) + config_meta_dir = os.path.join(directory, metomi.rose.CONFIG_META_DIR) + meta_file = os.path.join(config_meta_dir, metomi.rose.META_CONFIG_NAME) if os.path.isfile(meta_file): return config_meta_dir, warning if locator is None: @@ -618,35 +620,36 @@ def load_meta_path(config=None, directory=None, is_upgrade=False, paths = opt_meta_paths + sys.path else: paths = sys.path - locator = rose.resource.ResourceLocator(paths=paths) - opt_node = config.get([rose.CONFIG_SECT_TOP, - rose.CONFIG_OPT_META_TYPE], no_ignore=True) + locator = metomi.rose.resource.ResourceLocator(paths=paths) + opt_node = config.get([metomi.rose.CONFIG_SECT_TOP, + metomi.rose.CONFIG_OPT_META_TYPE], no_ignore=True) ignore_meta_error = opt_node is None if opt_node is None: - opt_node = config.get([rose.CONFIG_SECT_TOP, - rose.CONFIG_OPT_PROJECT], no_ignore=True) + opt_node = config.get([metomi.rose.CONFIG_SECT_TOP, + metomi.rose.CONFIG_OPT_PROJECT], no_ignore=True) if opt_node is None or not opt_node.value: meta_keys = ["rose-all"] else: key = str(opt_node.value) split_key = key.split('/') if len(split_key) == 1: - key = '/'.join([key, rose.META_DEFAULT_VN_DIR]) + key = '/'.join([key, metomi.rose.META_DEFAULT_VN_DIR]) meta_keys = [key] split_key = split_key if len(split_key) == 1 else split_key[:-1] if is_upgrade: meta_keys = ['/'.join(split_key)] else: - default_key = '/'.join(split_key + [rose.META_DEFAULT_VN_DIR]) + default_key = '/'.join( + split_key + [metomi.rose.META_DEFAULT_VN_DIR]) if default_key != key: meta_keys.append(default_key) for i, meta_key in enumerate(meta_keys): - path = os.path.join(meta_key, rose.META_CONFIG_NAME) + path = os.path.join(meta_key, metomi.rose.META_CONFIG_NAME) if is_upgrade: path = meta_key try: meta_path = locator.locate(path) - except rose.resource.ResourceError: + except metomi.rose.resource.ResourceError: continue else: if not (ignore_meta_error or 'version' in no_warn) and i > 0: @@ -670,36 +673,36 @@ def load_meta_config_tree(config, directory=None, config_type=None, paths = sys.path if error_handler is None: error_handler = _report_error - meta_list = ["rose-all/" + rose.META_CONFIG_NAME] + meta_list = ["rose-all/" + metomi.rose.META_CONFIG_NAME] if config_type is not None: default_meta_dir = config_type.replace(".", "-") - meta_list.append(default_meta_dir + "/" + rose.META_CONFIG_NAME) + meta_list.append(default_meta_dir + "/" + metomi.rose.META_CONFIG_NAME) config_meta_path, warning = load_meta_path( config, directory, opt_meta_paths=opt_meta_paths, no_warn=no_warn) if warning is not None and not ignore_meta_error: error_handler(text=warning) - locator = rose.resource.ResourceLocator(paths=paths) - opt_node = config.get([rose.CONFIG_SECT_TOP, - rose.CONFIG_OPT_META_TYPE], no_ignore=True) + locator = metomi.rose.resource.ResourceLocator(paths=paths) + opt_node = config.get([metomi.rose.CONFIG_SECT_TOP, + metomi.rose.CONFIG_OPT_META_TYPE], no_ignore=True) ignore_meta_error = ignore_meta_error or opt_node is None meta_config_tree = None - meta_config = rose.config.ConfigNode() + meta_config = metomi.rose.config.ConfigNode() for meta_key in meta_list: try: meta_path = locator.locate(meta_key) - except rose.resource.ResourceError: + except metomi.rose.resource.ResourceError: if not ignore_meta_error: error_handler(text=ERROR_LOAD_META_PATH.format(meta_key)) continue try: - meta_config_tree = rose.config_tree.ConfigTreeLoader().load( + meta_config_tree = metomi.rose.config_tree.ConfigTreeLoader().load( os.path.dirname(meta_path), - rose.META_CONFIG_NAME, + metomi.rose.META_CONFIG_NAME, conf_dir_paths=list(paths), conf_node=meta_config ) - except rose.config.ConfigSyntaxError as exc: + except metomi.rose.config.ConfigSyntaxError as exc: error_handler(text=str(exc)) else: meta_config = meta_config_tree.node @@ -707,15 +710,15 @@ def load_meta_config_tree(config, directory=None, config_type=None, return meta_config_tree # Try and get a proper non-default meta config tree. try: - meta_config_tree = rose.config_tree.ConfigTreeLoader().load( + meta_config_tree = metomi.rose.config_tree.ConfigTreeLoader().load( config_meta_path, - rose.META_CONFIG_NAME, + metomi.rose.META_CONFIG_NAME, conf_dir_paths=list(paths) ) - except rose.resource.ResourceError: + except metomi.rose.resource.ResourceError: if not ignore_meta_error: error_handler(text=ERROR_LOAD_META_PATH.format(meta_list)) - except rose.config.ConfigSyntaxError as exc: + except metomi.rose.config.ConfigSyntaxError as exc: error_handler(text=str(exc)) meta_config += meta_config_tree.node @@ -749,7 +752,7 @@ def load_meta_macro_modules(meta_files, module_prefix=None): try: modules.append(SourceFileLoader(as_name, meta_file).load_module()) except Exception: - rose.reporter.Reporter()( + metomi.rose.reporter.Reporter()( MacroLoadError(meta_file, traceback.format_exc())) sys.path.pop(0) modules.sort(key=str) @@ -788,7 +791,8 @@ def get_macros_for_config(config=None, """Driver function to return macro names for a config object. kwargs: - config - The config to retrieve macros for as a rose.config.ConfigNode + config - The config to retrieve macros for as a + metomi.rose.config.ConfigNode config_directory - The directory that the config file is located in. return_modules - If true then a list of macro modules is also returned. include_system - Include default rose macros? @@ -796,7 +800,7 @@ def get_macros_for_config(config=None, no_warn - Output metadata warnings? """ if config is None: - config = rose.config.ConfigNode() + config = ConfigNode() meta_config_tree = load_meta_config_tree( config, directory=config_directory, no_warn=no_warn) if meta_config_tree is None: @@ -807,8 +811,8 @@ def get_macros_for_config(config=None, os.path.join(v, k) for k, v in meta_config_tree.files.items()] modules.extend(load_meta_macro_modules(meta_filepaths)) if include_system: # Default macros. - import rose.macros # Done to avoid cyclic top-level imports. - modules.append(rose.macros) + import metomi.rose.macros # Done to avoid cyclic top-level imports. + modules.append(metomi.rose.macros) if return_modules: return get_macro_class_methods(modules), modules return get_macro_class_methods(modules) @@ -822,7 +826,7 @@ def check_config_integrity(app_config): return MacroReturnedCorruptConfigError(str(exc)) keys_and_nodes.insert(0, ([], app_config)) for keys, node in keys_and_nodes: - if not isinstance(node, rose.config.ConfigNode): + if not isinstance(node, metomi.rose.config.ConfigNode): return MacroReturnedCorruptConfigError(ERROR_RETURN_TYPE.format( node, "node", type(node), "rose.config.ConfigNode")) if (not isinstance(node.value, dict) and @@ -834,9 +838,11 @@ def check_config_integrity(app_config): if not isinstance(node.state, str): return MacroReturnedCorruptConfigError(ERROR_RETURN_TYPE.format( node.state, "node.state", type(node.state), "basestring")) - if node.state not in [rose.config.ConfigNode.STATE_NORMAL, - rose.config.ConfigNode.STATE_SYST_IGNORED, - rose.config.ConfigNode.STATE_USER_IGNORED]: + if node.state not in [ + metomi.rose.config.ConfigNode.STATE_NORMAL, + metomi.rose.config.ConfigNode.STATE_SYST_IGNORED, + metomi.rose.config.ConfigNode.STATE_USER_IGNORED + ]: return MacroReturnedCorruptConfigError( ERROR_RETURN_VALUE_STATE.format(node.state)) if not isinstance(node.comments, list): @@ -957,7 +963,7 @@ def pretty_format_config(config, ignore_error=False): """Standardise the keys and values of a config node. Args: - config (rose.config.ConfigNode): The Config node to convert. + config (metomi.rose.config.ConfigNode): The Config node to convert. """ for s_key, s_node in config.value.items(): @@ -965,14 +971,14 @@ def pretty_format_config(config, ignore_error=False): if ":" in scheme: scheme = scheme.split(":", 1)[0] try: - scheme_module = getattr(rose.formats, scheme) + scheme_module = getattr(metomi.rose.formats, scheme) pretty_format_keys = getattr(scheme_module, "pretty_format_keys") pretty_format_value = getattr(scheme_module, "pretty_format_value") except AttributeError: continue for keylist, node in list(s_node.walk()): # FIXME: Surely, only the scheme knows how to split its array? - values = rose.variable.array_split(node.value, ",") + values = metomi.rose.variable.array_split(node.value, ",") node.value = pretty_format_value(values) new_keylist = pretty_format_keys(keylist) if new_keylist != keylist: @@ -988,7 +994,7 @@ def standard_format_config(config): """Standardise any degenerate representations e.g. namelist repeats. Args: - config (rose.config.ConfigNode): The config node to convert. + config (metomi.rose.config.ConfigNode): The config node to convert. """ for keylist, node in config.walk(): @@ -997,11 +1003,11 @@ def standard_format_config(config): if ":" in scheme: scheme = scheme.split(":", 1)[0] try: - scheme_module = getattr(rose.formats, scheme) + scheme_module = getattr(metomi.rose.formats, scheme) standard_format = getattr(scheme_module, "standard_format") except AttributeError: continue - values = rose.variable.array_split(node.value, ",") + values = metomi.rose.variable.array_split(node.value, ",") node.value = standard_format(values) @@ -1010,7 +1016,7 @@ def get_metadata_for_config_id(setting_id, meta_config): Args: setting_id (str): The name of the setting to extract metadata for. - meta_config (rose.config.ConfigNode): Config node containing the + meta_config (metomi.rose.config.ConfigNode): Config node containing the metadata to extract from. Return: @@ -1038,14 +1044,14 @@ def get_metadata_for_config_id(setting_id, meta_config): >>> get_metadata_for_config_id('foo=bar', meta_config) {'values': '1,2,3', 'id': 'foo=bar'} - .. testcleanup:: rose.macro.get_metadata_for_config_id + .. testcleanup:: metomi.rose.macro.get_metadata_for_config_id test_cleanup(['rose-app.conf', 'meta/rose-meta.conf', 'meta']) """ metadata = {} - if rose.CONFIG_DELIMITER in setting_id: - option = setting_id.split(rose.CONFIG_DELIMITER, 1)[1] + if metomi.rose.CONFIG_DELIMITER in setting_id: + option = setting_id.split(metomi.rose.CONFIG_DELIMITER, 1)[1] search_option = REC_ID_STRIP_DUPL.sub("", option) else: option = None @@ -1060,35 +1066,35 @@ def get_metadata_for_config_id(setting_id, meta_config): for opt, opt_node in node.value.items(): if not opt_node.is_ignored(): metadata.update({opt: opt_node.value}) - if option is None and rose.META_PROP_TITLE in metadata: + if option is None and metomi.rose.META_PROP_TITLE in metadata: # Handle section modifier titles modifier = search_id.replace(no_modifier_id, "") - metadata[rose.META_PROP_TITLE] += " " + modifier + metadata[metomi.rose.META_PROP_TITLE] += " " + modifier if (setting_id != search_id and - rose.META_PROP_DUPLICATE in metadata): + metomi.rose.META_PROP_DUPLICATE in metadata): # foo{bar}(1) cannot inherit duplicate from foo. - metadata.pop(rose.META_PROP_DUPLICATE) + metadata.pop(metomi.rose.META_PROP_DUPLICATE) node = meta_config.get([search_id], no_ignore=True) # If modifier, get metadata for namelist:foo{bar} if node is not None: for opt, opt_node in node.value.items(): if not opt_node.is_ignored(): metadata.update({opt: opt_node.value}) - if rose.META_PROP_TITLE in metadata: + if metomi.rose.META_PROP_TITLE in metadata: # Handle duplicate (indexed) settings sharing a title if option is None: if search_id != setting_id: # Handle duplicate sections titles - metadata.pop(rose.META_PROP_TITLE) + metadata.pop(metomi.rose.META_PROP_TITLE) elif search_option != option: # Handle duplicate options titles index = option.replace(search_option, "") - metadata[rose.META_PROP_TITLE] += " " + index - if (rose.META_PROP_LENGTH in metadata and + metadata[metomi.rose.META_PROP_TITLE] += " " + index + if (metomi.rose.META_PROP_LENGTH in metadata and option is not None and search_option != option and REC_ID_SINGLE_ELEMENT.search(option)): # Option is a single element in an array, not a slice. - metadata.pop(rose.META_PROP_LENGTH) + metadata.pop(metomi.rose.META_PROP_LENGTH) metadata.update({'id': setting_id}) return metadata @@ -1100,7 +1106,7 @@ def run_macros(config_map, meta_config, config_name, macro_names, no_warn=False, default_only=False): """Run standard or custom macros for a configuration.""" - reporter = rose.reporter.Reporter(verbosity) + reporter = metomi.rose.reporter.Reporter(verbosity) macro_tuples, modules = get_macros_for_config( config_map[None], opt_conf_dir, @@ -1122,8 +1128,8 @@ def run_macros(config_map, meta_config, config_name, macro_names, for module_name, class_name, method, _ in macro_tuples: if opt_fix and not opt_transform_all: # Only include internal transformer macros for - # rose macro --fix. - if module_name != rose.macros.__name__: + # metomi.rose macro --fix. + if module_name != metomi.rose.macros.__name__: continue if method == macro_method: macro_name = ".".join([module_name, class_name]) @@ -1241,8 +1247,8 @@ def report_sort(report1, report2): # This logic replicates output of the deprecated Python2 `cmp` # builtin return (str(opt1) > str(opt2)) - (str(opt1) < str(opt2)) - return rose.config.sort_settings(opt1, opt2) - return rose.config.sort_settings(sect1, sect2) + return metomi.rose.config.sort_settings(opt1, opt2) + return metomi.rose.config.sort_settings(sect1, sect2) def get_reports_as_text(config_reports_map, macro_id, @@ -1396,7 +1402,7 @@ def apply_macro_to_config_map(config_map, meta_config, macro_function, len(return_value) != 2): raise ValueError(err_bad_return_value) new_config, change_list = return_value - if (not isinstance(new_config, rose.config.ConfigNode) or + if (not isinstance(new_config, metomi.rose.config.ConfigNode) or not isinstance(change_list, list)): raise ValueError(err_bad_return_value) exception = check_config_integrity(new_config) @@ -1442,12 +1448,12 @@ def get_user_values(options, ignore=None): options[key] = ast.literal_eval(user_input) entered = True except (SyntaxError, ValueError): - rose.reporter.Reporter()( + metomi.rose.reporter.Reporter()( "Invalid entry: Input should be a valid python " "value.\nNote that strings should be quoted. " "Please try again:\n", - kind=rose.reporter.Reporter.KIND_ERR, - level=rose.reporter.Reporter.FAIL + kind=metomi.rose.reporter.Reporter.KIND_ERR, + level=metomi.rose.reporter.Reporter.FAIL ) else: entered = True @@ -1455,7 +1461,7 @@ def get_user_values(options, ignore=None): def dump_config(config, opt_conf_dir, opt_output_dir=None, - conf_key=None, name=rose.SUB_CONFIG_NAME): + conf_key=None, name=metomi.rose.SUB_CONFIG_NAME): """Dump the config in a standard form.""" config = copy.deepcopy(config) pretty_format_config(config) @@ -1469,27 +1475,28 @@ def dump_config(config, opt_conf_dir, opt_output_dir=None, source_root, source_ext = os.path.splitext(name) base = source_root + "-" + conf_key + source_ext target_path = os.path.join( - directory, rose.config.OPT_CONFIG_DIR, base) - rose.config.dump(config, target_path) + directory, metomi.rose.config.OPT_CONFIG_DIR, base) + metomi.rose.config.dump(config, target_path) def load_conf_from_file(conf_dir, config_file_path, mode="macro"): """Loads config data from the file config_file_path.""" is_info_config = (os.path.basename(config_file_path) == - rose.INFO_CONFIG_NAME) + metomi.rose.INFO_CONFIG_NAME) optional_keys = [] - optional_dir = os.path.join(conf_dir, rose.config.OPT_CONFIG_DIR) - optional_glob = os.path.join(optional_dir, rose.GLOB_OPT_CONFIG_FILE) + optional_dir = os.path.join(conf_dir, metomi.rose.config.OPT_CONFIG_DIR) + optional_glob = os.path.join( + optional_dir, metomi.rose.GLOB_OPT_CONFIG_FILE) for path in glob.glob(optional_glob): filename = os.path.basename(path) # filename is a null string if path is to a directory. - result = re.match(rose.RE_OPT_CONFIG_FILE, filename) + result = re.match(metomi.rose.RE_OPT_CONFIG_FILE, filename) if not result: continue optional_keys.append(result.group(1)) # Load the configuration and the metadata macros. - config_loader = rose.config.ConfigLoader() + config_loader = metomi.rose.config.ConfigLoader() if is_info_config: optional_keys = None app_config, config_map = config_loader.load_with_opts( @@ -1500,7 +1507,7 @@ def load_conf_from_file(conf_dir, config_file_path, mode="macro"): standard_format_config(config) # Load meta config if it exists. - meta_config = rose.config.ConfigNode() + meta_config = metomi.rose.config.ConfigNode() meta_path, warning = load_meta_path(app_config, conf_dir) if meta_path is None and not is_info_config: @@ -1508,9 +1515,10 @@ def load_conf_from_file(conf_dir, config_file_path, mode="macro"): text = ERROR_LOAD_METADATA.format("") if warning: text = warning - rose.reporter.Reporter()(text, - kind=rose.reporter.Reporter.KIND_ERR, - level=rose.reporter.Reporter.FAIL) + metomi.rose.reporter.Reporter()( + text, + kind=metomi.rose.reporter.Reporter.KIND_ERR, + level=metomi.rose.reporter.Reporter.FAIL) return None else: meta_config = load_meta_config(app_config, @@ -1544,20 +1552,21 @@ def parse_macro_args(argv=None): def _report_error(exception=None, text=""): - """Report an error via rose.reporter utilities.""" + """Report an error via metomi.rose.reporter utilities.""" if text: text += "\n" if exception is not None: text += type(exception).__name__ + ": " + str(exception) + "\n" - rose.reporter.Reporter()( + metomi.rose.reporter.Reporter()( text + "\n", - kind=rose.reporter.Reporter.KIND_ERR, - level=rose.reporter.Reporter.FAIL + kind=metomi.rose.reporter.Reporter.KIND_ERR, + level=metomi.rose.reporter.Reporter.FAIL ) def scan_rose_directory(conf_dir, suite_only=False): - """Returns a list of rose config files found within the given conf_dir. + """Returns a list of rose config files found within the given + conf_dir. * If the conf_dir is an application directory then return only the application configuration file @@ -1573,22 +1582,25 @@ def scan_rose_directory(conf_dir, suite_only=False): path = conf_dir while True: lstdir = set(os.listdir(path)) - if rose.TOP_CONFIG_NAME in lstdir: + if metomi.rose.TOP_CONFIG_NAME in lstdir: # We are in the suite directory. confs = [] if not suite_only: # Add app/*/rose-app.conf files. - confs = sorted(glob.glob(os.path.join( - path, rose.SUB_CONFIGS_DIR, '*', rose.SUB_CONFIG_NAME))) - # Add rose-suite.conf file. - confs.append(os.path.join(path, rose.TOP_CONFIG_NAME)) - # Add rose-suite.info file. - if rose.INFO_CONFIG_NAME in lstdir: - confs.append(os.path.join(path, rose.INFO_CONFIG_NAME)) + confs = sorted(glob.glob( + os.path.join( + path, metomi.rose.SUB_CONFIGS_DIR, + '*', + metomi.rose.SUB_CONFIG_NAME))) + # Add metomi.rose-suite.conf file. + confs.append(os.path.join(path, metomi.rose.TOP_CONFIG_NAME)) + # Add metomi.rose-suite.info file. + if metomi.rose.INFO_CONFIG_NAME in lstdir: + confs.append(os.path.join(path, metomi.rose.INFO_CONFIG_NAME)) return confs - elif not suite_only and rose.SUB_CONFIG_NAME in lstdir: + elif not suite_only and metomi.rose.SUB_CONFIG_NAME in lstdir: # We are in an app directory. Return only that app. - return [os.path.join(path, rose.SUB_CONFIG_NAME)] + return [os.path.join(path, metomi.rose.SUB_CONFIG_NAME)] # Go up a directory. path = os.path.dirname(path) if path == os.path.dirname(path): @@ -1598,8 +1610,8 @@ def scan_rose_directory(conf_dir, suite_only=False): def main(): - """Run rose macro.""" - reporter = rose.reporter.Reporter() + """Run metomi.rose macro.""" + reporter = metomi.rose.reporter.Reporter() add_meta_paths() opts, args = parse_macro_args() @@ -1609,19 +1621,19 @@ def main(): # Fail if no config files could be found. if not confs: reporter(ERROR_LOAD_CONFIG_DIR.format(opts.conf_dir), - kind=rose.reporter.Reporter.KIND_ERR, - level=rose.reporter.Reporter.FAIL) + kind=metomi.rose.reporter.Reporter.KIND_ERR, + level=metomi.rose.reporter.Reporter.FAIL) sys.exit(1) # Fail if --output-dir specified and multiple config files found. if len(confs) > 1 and opts.output_dir: reporter(ERROR_OUT_DIR_MULTIPLE_APPS, - kind=rose.reporter.Reporter.KIND_ERR, - level=rose.reporter.Reporter.FAIL) + kind=metomi.rose.reporter.Reporter.KIND_ERR, + level=metomi.rose.reporter.Reporter.FAIL) sys.exit(1) # Path manipulation. - sys.path.append(os.getenv("ROSE_HOME")) + sys.path.append(os.getenv("ROSE_LIB")) add_opt_meta_paths(opts.meta_path) # Run macros for each config. @@ -1643,9 +1655,9 @@ def main(): # Report which config we are currently working on. if len(confs) > 1: - if cur_conf_type == rose.SUB_CONFIG_NAME: + if cur_conf_type == metomi.rose.SUB_CONFIG_NAME: reporter(os.path.join( - rose.SUB_CONFIGS_DIR, config_name, cur_conf_type)) + metomi.rose.SUB_CONFIGS_DIR, config_name, cur_conf_type)) else: reporter(cur_conf_type) sys.stdout.flush() @@ -1656,7 +1668,7 @@ def main(): opts.fix, opts.non_interactive, opts.output_dir, opts.validate_all, opts.transform_all, verbosity, no_warn=opts.no_warn, - default_only=cur_conf_type == rose.INFO_CONFIG_NAME + default_only=cur_conf_type == metomi.rose.INFO_CONFIG_NAME )) # Fail if any macro failed. diff --git a/lib/python/rose/macros/__init__.py b/metomi/rose/macros/__init__.py similarity index 82% rename from lib/python/rose/macros/__init__.py rename to metomi/rose/macros/__init__.py index d8b8390d9b..6154c707bb 100644 --- a/lib/python/rose/macros/__init__.py +++ b/metomi/rose/macros/__init__.py @@ -21,7 +21,7 @@ Module to contain internal system macros for operating on a configuration. """ -import rose.macro +import metomi.rose.macro from . import compulsory from . import duplicate from . import format @@ -33,15 +33,15 @@ MODULES = [compulsory, duplicate, format, rule, trigger, value] -class DefaultTransforms(rose.macro.MacroTransformerCollection): +class DefaultTransforms(metomi.rose.macro.MacroTransformerCollection): """Runs all the default fixers, such as trigger fixing.""" def __init__(self): macros = [] - macro_info_tuples = rose.macro.get_macro_class_methods(MODULES) + macro_info_tuples = metomi.rose.macro.get_macro_class_methods(MODULES) for module_name, class_name, method, _ in macro_info_tuples: - if method == rose.macro.TRANSFORM_METHOD: + if method == metomi.rose.macro.TRANSFORM_METHOD: for module in MODULES: if module.__name__ == module_name: macro_inst = getattr(module, class_name)() @@ -49,15 +49,15 @@ def __init__(self): super(DefaultTransforms, self).__init__(*macros) -class DefaultValidators(rose.macro.MacroValidatorCollection): +class DefaultValidators(metomi.rose.macro.MacroValidatorCollection): """Runs all the default checks, such as compulsory checking.""" def __init__(self): macros = [] - macro_info_tuples = rose.macro.get_macro_class_methods(MODULES) + macro_info_tuples = metomi.rose.macro.get_macro_class_methods(MODULES) for module_name, class_name, method, _ in macro_info_tuples: - if method == rose.macro.VALIDATE_METHOD: + if method == metomi.rose.macro.VALIDATE_METHOD: for module in MODULES: if module.__name__ == module_name: macro_inst = getattr(module, class_name)() diff --git a/lib/python/rose/macros/compulsory.py b/metomi/rose/macros/compulsory.py similarity index 84% rename from lib/python/rose/macros/compulsory.py rename to metomi/rose/macros/compulsory.py index ab21e28dc6..b6bed94fee 100644 --- a/lib/python/rose/macros/compulsory.py +++ b/metomi/rose/macros/compulsory.py @@ -19,9 +19,9 @@ # ----------------------------------------------------------------------------- -import rose.config -import rose.macro -import rose.variable +import metomi.rose.config +import metomi.rose.macro +import metomi.rose.variable _OPTIONS_KEY = "options" @@ -29,7 +29,7 @@ _SECTION_IS_COMPULSORY_KEY = "section is compulsory" -class CompulsoryChecker(rose.macro.MacroBaseRoseEdit): +class CompulsoryChecker(metomi.rose.macro.MacroBaseRoseEdit): """Returns sections and options that are compulsory but missing. @@ -58,8 +58,8 @@ def get_compulsory_data(self, meta_config): for setting_id, sect_node in meta_config.value.items(): if sect_node.is_ignored() or isinstance(sect_node.value, str): continue - if (sect_node.get_value([rose.META_PROP_COMPULSORY]) == - rose.META_PROP_VALUE_TRUE): + if (sect_node.get_value([metomi.rose.META_PROP_COMPULSORY]) == + metomi.rose.META_PROP_VALUE_TRUE): config_sect, config_opt = ( self._get_section_option_from_id(setting_id)) compulsory_data.setdefault( @@ -74,29 +74,29 @@ def get_compulsory_data(self, meta_config): else: compulsory_data[config_sect][_OPTIONS_KEY].append( config_opt) - if (sect_node.get_value([rose.META_PROP_DUPLICATE]) == - rose.META_PROP_VALUE_TRUE): + if (sect_node.get_value([metomi.rose.META_PROP_DUPLICATE]) == + metomi.rose.META_PROP_VALUE_TRUE): compulsory_data[config_sect][_REPORTED_SECTION_KEY] = ( config_sect + "({0})".format( - rose.CONFIG_SETTING_INDEX_DEFAULT) + metomi.rose.CONFIG_SETTING_INDEX_DEFAULT) ) return compulsory_data def validate(self, config_data, meta_config): """Return a list of compulsory-related errors, if any. - config_data - a rose.config.ConfigNode or a dictionary that + config_data - a metomi.rose.config.ConfigNode or a dictionary that looks like this: {"sections": - {"namelist:foo": rose.section.Section instance, - "env": rose.section.Section instance}, + {"namelist:foo": metomi.rose.section.Section instance, + "env": metomi.rose.section.Section instance}, "variables": - {"namelist:foo": [rose.variable.Variable instance, - rose.variable.Variable instance], - "env": [rose.variable.Variable instance] + {"namelist:foo": [metomi.rose.variable.Variable instance, + metomi.rose.variable.Variable instance], + "env": [metomi.rose.variable.Variable instance] } } - meta_config - a rose.config.ConfigNode. + meta_config - a metomi.rose.config.ConfigNode. """ return self.validate_settings(config_data, meta_config) @@ -105,18 +105,18 @@ def validate_settings(self, config_data, meta_config, only_these_sections=None): """Return a list of compulsory-related errors, if any. - config_data - a rose.config.ConfigNode or a dictionary that + config_data - a metomi.rose.config.ConfigNode or a dictionary that looks like this: {"sections": - {"namelist:foo": rose.section.Section instance, - "env": rose.section.Section instance}, + {"namelist:foo": metomi.rose.section.Section instance, + "env": metomi.rose.section.Section instance}, "variables": - {"namelist:foo": [rose.variable.Variable instance, - rose.variable.Variable instance], - "env": [rose.variable.Variable instance] + {"namelist:foo": [metomi.rose.variable.Variable instance, + metomi.rose.variable.Variable instance], + "env": [metomi.rose.variable.Variable instance] } } - meta_config - a rose.config.ConfigNode. + meta_config - a metomi.rose.config.ConfigNode. only_these_sections (default None) - a list of sections to examine. If specified, checking for other sections will be skipped. @@ -176,7 +176,7 @@ def validate_settings(self, config_data, meta_config, config_data, alias_section): if (option == alias_option or (alias_option.startswith(option) and - rose.macro.REC_ID_STRIP_DUPL.sub( + metomi.rose.macro.REC_ID_STRIP_DUPL.sub( "", alias_option) == option)): setting_id = self._get_id_from_section_option( alias_section, alias_option) @@ -190,7 +190,7 @@ def validate_settings(self, config_data, meta_config, # Check that ids that we have found are not user-ignored. for setting_id in check_user_ignored_ids: if (self._get_config_id_state(config_data, setting_id) == - rose.config.ConfigNode.STATE_USER_IGNORED): + metomi.rose.config.ConfigNode.STATE_USER_IGNORED): value = self._get_config_id_value(config_data, setting_id) section, option = self._get_section_option_from_id(setting_id) self.add_report(section, option, value, @@ -202,9 +202,9 @@ def _generate_aliases_for_sections(self, config_data): for section in self._get_config_sections(config_data): if section not in self.alias_section_to_basics: basic_section_no_modifier = ( - rose.macro.REC_ID_STRIP.sub('', section)) + metomi.rose.macro.REC_ID_STRIP.sub('', section)) basic_section_keep_modifier = ( - rose.macro.REC_ID_STRIP_DUPL.sub('', section)) + metomi.rose.macro.REC_ID_STRIP_DUPL.sub('', section)) basic_sections = set([basic_section_no_modifier, basic_section_keep_modifier]) for basic_section in basic_sections: @@ -215,7 +215,7 @@ def _generate_aliases_for_sections(self, config_data): self.basic_section_aliases[basic_section].append(section) -class CompulsoryChanger(rose.macro.MacroBase): +class CompulsoryChanger(metomi.rose.macro.MacroBase): """Add sections and options that are compulsory but missing. @@ -254,9 +254,9 @@ def transform(self, config, meta_config=None): if opt is None: continue var_id = self._get_id_from_section_option(sect, opt) - metadata = rose.macro.get_metadata_for_config_id( + metadata = metomi.rose.macro.get_metadata_for_config_id( var_id, meta_config) - value = rose.variable.get_value_from_metadata(metadata) + value = metomi.rose.variable.get_value_from_metadata(metadata) config.set([sect, opt], value) self.add_report(sect, opt, value, self.ADD_COMPULSORY_OPT) diff --git a/lib/python/rose/macros/duplicate.py b/metomi/rose/macros/duplicate.py similarity index 85% rename from lib/python/rose/macros/duplicate.py rename to metomi/rose/macros/duplicate.py index 594c42727c..0dae12bc7e 100644 --- a/lib/python/rose/macros/duplicate.py +++ b/metomi/rose/macros/duplicate.py @@ -20,10 +20,10 @@ from functools import cmp_to_key -import rose.macro +import metomi.rose.macro -class DuplicateChecker(rose.macro.MacroBase): +class DuplicateChecker(metomi.rose.macro.MacroBase): """Returns settings whose duplicate status does not match their name.""" @@ -35,16 +35,16 @@ def validate(self, config, meta_config=None): self.reports = [] sect_error_no_dupl = {} sect_keys = list(config.value) - sorter = rose.config.sort_settings + sorter = metomi.rose.config.sort_settings sect_keys.sort(key=cmp_to_key(sorter)) for section in sect_keys: node = config.get([section]) if not isinstance(node.value, dict): continue metadata = self.get_metadata_for_config_id(section, meta_config) - duplicate = metadata.get(rose.META_PROP_DUPLICATE) - is_duplicate = duplicate == rose.META_PROP_VALUE_TRUE - basic_section = rose.macro.REC_ID_STRIP.sub("", section) + duplicate = metadata.get(metomi.rose.META_PROP_DUPLICATE) + is_duplicate = duplicate == metomi.rose.META_PROP_VALUE_TRUE + basic_section = metomi.rose.macro.REC_ID_STRIP.sub("", section) if is_duplicate: if basic_section == section: self.add_report(section, None, None, @@ -52,7 +52,7 @@ def validate(self, config, meta_config=None): elif section != basic_section: if basic_section not in sect_error_no_dupl: sect_error_no_dupl.update({basic_section: 1}) - no_index_section = rose.macro.REC_ID_STRIP_DUPL.sub( + no_index_section = metomi.rose.macro.REC_ID_STRIP_DUPL.sub( "", section) if no_index_section != section: basic_section = no_index_section @@ -72,7 +72,7 @@ def _get_has_metadata(self, metadata, basic_section, meta_config): continue if ((meta_section == basic_section or meta_section.startswith( - basic_section + rose.CONFIG_DELIMITER)) and + basic_section + metomi.rose.CONFIG_DELIMITER)) and isinstance(meta_node.value, dict)): return True return False diff --git a/lib/python/rose/macros/format.py b/metomi/rose/macros/format.py similarity index 79% rename from lib/python/rose/macros/format.py rename to metomi/rose/macros/format.py index d0113707b1..be5810302f 100644 --- a/lib/python/rose/macros/format.py +++ b/metomi/rose/macros/format.py @@ -5,25 +5,25 @@ import inspect -import rose.formats -import rose.macro +import metomi.rose.formats +import metomi.rose.macro TRANSFORM_FUNC_NAME = "transform_config" VALIDATE_FUNC_NAME = "validate_config" -class FormatChecker(rose.macro.MacroBase): +class FormatChecker(metomi.rose.macro.MacroBase): """Validates against format specifications and conventions.""" def validate(self, config, meta_config=None): """Return a list of errors, if any.""" self.reports = [] - for attr_name in dir(rose.formats): + for attr_name in dir(metomi.rose.formats): if attr_name.startswith("_"): continue - attr = getattr(rose.formats, attr_name) + attr = getattr(metomi.rose.formats, attr_name) if inspect.ismodule(attr) and hasattr(attr, VALIDATE_FUNC_NAME): func = getattr(attr, VALIDATE_FUNC_NAME) if inspect.isfunction(func): @@ -31,17 +31,17 @@ def validate(self, config, meta_config=None): return self.reports -class FormatFixer(rose.macro.MacroBase): +class FormatFixer(metomi.rose.macro.MacroBase): """Transforms a configuration based on format specifications.""" def transform(self, config, meta_config=None): """Return a config and a list of changes, if any.""" self.reports = [] - for attr_name in dir(rose.formats): + for attr_name in dir(metomi.rose.formats): if attr_name.startswith("_"): continue - attr = getattr(rose.formats, attr_name) + attr = getattr(metomi.rose.formats, attr_name) if inspect.ismodule(attr) and hasattr(attr, TRANSFORM_FUNC_NAME): func = getattr(attr, TRANSFORM_FUNC_NAME) if inspect.isfunction(func): diff --git a/lib/python/rose/macros/rule.py b/metomi/rose/macros/rule.py similarity index 94% rename from lib/python/rose/macros/rule.py rename to metomi/rose/macros/rule.py index fa42157d57..dab6cad515 100644 --- a/lib/python/rose/macros/rule.py +++ b/metomi/rose/macros/rule.py @@ -24,8 +24,8 @@ import jinja2 import jinja2.exceptions -import rose.macro -import rose.variable +import metomi.rose.macro +import metomi.rose.variable REC_EXPR_IS_THIS_RULE = re.compile( @@ -62,7 +62,7 @@ def __str__(self): return "{0} - could not retrieve value. ".format(arg_string) -class FailureRuleChecker(rose.macro.MacroBase): +class FailureRuleChecker(metomi.rose.macro.MacroBase): """Check the fail-if and warn-if conditions""" @@ -86,8 +86,8 @@ def validate(self, config, meta_config): sect, opt = node_keys value = node.value setting_id = self._get_id_from_section_option(sect, opt) - metadata = rose.macro.get_metadata_for_config_id(setting_id, - meta_config) + metadata = metomi.rose.macro.get_metadata_for_config_id( + setting_id, meta_config) for rule_opt in [self.RULE_ERROR_NAME, self.RULE_WARNING_NAME]: if rule_opt in metadata: rule = metadata.get(rule_opt) @@ -146,7 +146,7 @@ def validate(self, config, meta_config): return self.reports -class RuleEvaluator(rose.macro.MacroBase): +class RuleEvaluator(metomi.rose.macro.MacroBase): """Evaluate logical expressions in the metadata.""" @@ -229,7 +229,8 @@ def _process_rule(self, rule, setting_id, config, meta_config, var_id = setting_id setting_value = get_value_from_id( var_id, config, meta_config, setting_id) - array_value = rose.variable.array_split(str(setting_value)) + array_value = metomi.rose.variable.array_split( + str(setting_value)) new_string = start + "(" for elem_num in range(1, len(array_value) + 1): new_string += self.ARRAY_EXPR.format(var_id, elem_num, @@ -248,7 +249,7 @@ def _process_rule(self, rule, setting_id, config, meta_config, var_id = var_id.replace("this", setting_id) setting_value = get_value_from_id( var_id, config, meta_config, setting_id) - array_value = rose.variable.array_split(str(setting_value)) + array_value = metomi.rose.variable.array_split(str(setting_value)) new_string = start + str(len(array_value)) + end rule = self.REC_LEN_FUNC.sub(new_string, rule, count=1) @@ -316,15 +317,16 @@ def _get_value_from_id(self, variable_id, config, meta_config, parent_id): section, option = self._get_section_option_from_id(variable_id) if variable_id != parent_id: # We may need to de-duplicate the section in the variable_id. - dupl_section = rose.macro.REC_ID_STRIP.sub("", section) + dupl_section = metomi.rose.macro.REC_ID_STRIP.sub("", section) dupl_node = meta_config.get( - [dupl_section, rose.META_PROP_DUPLICATE], no_ignore=True) + [dupl_section, metomi.rose.META_PROP_DUPLICATE], + no_ignore=True) if (dupl_node is not None and - dupl_node.value == rose.META_PROP_VALUE_TRUE): + dupl_node.value == metomi.rose.META_PROP_VALUE_TRUE): # This is an id in a duplicate namelist. parent_section = self._get_section_option_from_id( parent_id)[0] - parent_dupl_section = rose.macro.REC_ID_STRIP.sub( + parent_dupl_section = metomi.rose.macro.REC_ID_STRIP.sub( "", parent_section) if parent_dupl_section != dupl_section: raise RuleValueError(variable_id) @@ -348,7 +350,7 @@ def _get_value_from_id(self, variable_id, config, meta_config, parent_id): index = int(element) except (TypeError, ValueError): raise RuleValueError(variable_id) - val_array = rose.variable.array_split(value) + val_array = metomi.rose.variable.array_split(value) try: return_value = val_array[index - 1] except IndexError: diff --git a/lib/python/rose/macros/trigger.py b/metomi/rose/macros/trigger.py similarity index 91% rename from lib/python/rose/macros/trigger.py rename to metomi/rose/macros/trigger.py index 53ff037638..44f49b32f7 100644 --- a/lib/python/rose/macros/trigger.py +++ b/metomi/rose/macros/trigger.py @@ -20,15 +20,15 @@ import copy -import rose.config -import rose.config_tree -import rose.env -import rose.macro -import rose.macros.rule -import rose.resource +import metomi.rose.config +import metomi.rose.config_tree +import metomi.rose.env +import metomi.rose.macro +import metomi.rose.macros.rule +import metomi.rose.resource -class TriggerMacro(rose.macro.MacroBaseRoseEdit): +class TriggerMacro(metomi.rose.macro.MacroBaseRoseEdit): """Class to load and check trigger dependencies.""" @@ -51,15 +51,18 @@ def _setup_triggers(self, meta_config): self.trigger_family_lookup = {} self._id_is_duplicate = {} # Speedup dictionary. self.enabled_dict = {} - self.evaluator = rose.macros.rule.RuleEvaluator() - self.rec_rule = rose.macros.rule.REC_EXPR_IS_THIS_RULE + self.evaluator = metomi.rose.macros.rule.RuleEvaluator() + self.rec_rule = metomi.rose.macros.rule.REC_EXPR_IS_THIS_RULE for setting_id, sect_node in meta_config.value.items(): if sect_node.is_ignored(): continue - opt_node = sect_node.get([rose.META_PROP_TRIGGER], no_ignore=True) + opt_node = sect_node.get([metomi.rose.META_PROP_TRIGGER], + no_ignore=True) if opt_node is not None: expr = opt_node.value - id_value_dict = rose.variable.parse_trigger_expression(expr) + id_value_dict = metomi.rose.variable.parse_trigger_expression( + expr + ) for trig_id, values in id_value_dict.items(): if not len(values): id_value_dict.update({trig_id: [None]}) @@ -73,9 +76,9 @@ def transform(self, config, meta_config=None): self._setup_triggers(meta_config) self.enabled_dict = {} self.ignored_dict = {} - enabled = rose.config.ConfigNode.STATE_NORMAL - trig_ignored = rose.config.ConfigNode.STATE_SYST_IGNORED - user_ignored = rose.config.ConfigNode.STATE_USER_IGNORED + enabled = metomi.rose.config.ConfigNode.STATE_NORMAL + trig_ignored = metomi.rose.config.ConfigNode.STATE_SYST_IGNORED + user_ignored = metomi.rose.config.ConfigNode.STATE_USER_IGNORED state_map = {enabled: 'enabled ', trig_ignored: 'trig-ignored', user_ignored: 'user-ignored'} @@ -125,18 +128,18 @@ def update(self, var_id, config_data, meta_config): var_id - a setting id to start the triggering update at. If an id has duplicates (e.g. is part of a duplicate section), update all duplicate ids. - config_data - a rose.config.ConfigNode or a dictionary that + config_data - a metomi.rose.config.ConfigNode or a dictionary that looks like this: {"sections": - {"namelist:foo": rose.section.Section instance, - "env": rose.section.Section instance}, + {"namelist:foo": metomi.rose.section.Section instance, + "env": metomi.rose.section.Section instance}, "variables": - {"namelist:foo": [rose.variable.Variable instance, - rose.variable.Variable instance], - "env": [rose.variable.Variable instance] + {"namelist:foo": [metomi.rose.variable.Variable instance, + metomi.rose.variable.Variable instance], + "env": [metomi.rose.variable.Variable instance] } } - meta_config - a rose.config.ConfigNode. + meta_config - a metomi.rose.config.ConfigNode. """ config_sections = self._get_config_sections(config_data) @@ -296,12 +299,12 @@ def _get_ranked_trigger_ids(self): def validate(self, config, meta_config=None): self.reports = [] if meta_config is None: - meta_config = rose.config.ConfigNode() + meta_config = metomi.rose.config.ConfigNode() if not hasattr(self, 'trigger_family_lookup'): self._setup_triggers(meta_config) - enabled = rose.config.ConfigNode.STATE_NORMAL - trig_ignored = rose.config.ConfigNode.STATE_SYST_IGNORED - user_ignored = rose.config.ConfigNode.STATE_USER_IGNORED + enabled = metomi.rose.config.ConfigNode.STATE_NORMAL + trig_ignored = metomi.rose.config.ConfigNode.STATE_SYST_IGNORED + user_ignored = metomi.rose.config.ConfigNode.STATE_USER_IGNORED state_map = {enabled: 'enabled ', trig_ignored: 'trig-ignored', user_ignored: 'user-ignored'} @@ -330,7 +333,7 @@ def validate_dependencies(self, config, meta_config): """Validate the trigger setup - e.g. check for cyclic dependencies.""" self.reports = [] if meta_config is None: - meta_config = rose.config.ConfigNode() + meta_config = metomi.rose.config.ConfigNode() if not hasattr(self, 'trigger_family_lookup'): self._setup_triggers(meta_config) config_sections = config.value @@ -363,7 +366,7 @@ def validate_dependencies(self, config, meta_config): if self.rec_rule.search(string): try: self.evaluate_trig_rule(string, start_id, '') - except rose.macros.rule.RuleValueError: + except metomi.rose.macros.rule.RuleValueError: continue except Exception: return self._get_error_report_for_id( @@ -419,12 +422,13 @@ def _get_duplicate_config_sections(self, config_data, def _get_family_dict(self, setting_id, config_data, meta_config): if self._check_is_id_dupl(setting_id, meta_config): sect, opt = self._get_section_option_from_id(setting_id) - base_sect = rose.macro.REC_ID_STRIP.sub("", sect) + base_sect = metomi.rose.macro.REC_ID_STRIP.sub("", sect) trig_id = self._get_id_from_section_option(base_sect, opt) items = list(self.trigger_family_lookup.get(trig_id, {}).items()) for i, (child_id, vals) in enumerate(items): ch_sect, ch_opt = self._get_section_option_from_id(child_id) - if rose.macro.REC_ID_STRIP.sub("", ch_sect) == base_sect: + if metomi.rose.macro.REC_ID_STRIP.sub("", ch_sect)\ + == base_sect: new_id = self._get_id_from_section_option(sect, ch_opt) items[i] = (new_id, vals) return dict(items) @@ -458,16 +462,17 @@ def _check_is_id_dupl(self, setting_id, meta_config): if setting_id not in self._id_is_duplicate: sect = self._get_section_option_from_id(setting_id)[0] # Note: when modifier metadata ticket goes in, change the regex. - sect = rose.macro.REC_ID_STRIP.sub("", sect) - node = meta_config.get([sect, rose.META_PROP_DUPLICATE]) + sect = metomi.rose.macro.REC_ID_STRIP.sub("", sect) + node = meta_config.get([sect, metomi.rose.META_PROP_DUPLICATE]) self._id_is_duplicate[setting_id] = ( - node is not None and node.value == rose.META_PROP_VALUE_TRUE) + node is not None + and node.value == metomi.rose.META_PROP_VALUE_TRUE) return self._id_is_duplicate[setting_id] def _get_stripped_id(self, setting_id, meta_config): if self._check_is_id_dupl(setting_id, meta_config): sect, opt = self._get_section_option_from_id(setting_id) - base_sect = rose.macro.REC_ID_STRIP.sub("", sect) + base_sect = metomi.rose.macro.REC_ID_STRIP.sub("", sect) return self._get_id_from_section_option(base_sect, opt) return setting_id @@ -503,7 +508,7 @@ def _check_values_ok(self, value, setting_id, allowed_values): else: if string == value: return True - return rose.env.contains_env_var(value) + return metomi.rose.env.contains_env_var(value) def evaluate_trig_rule(self, rule, setting_id, value): """Launch an evaluation of a custom trigger expression.""" @@ -511,9 +516,9 @@ def evaluate_trig_rule(self, rule, setting_id, value): return self._evaluated_rule_checks[(rule, value)] except KeyError: section, option = self._get_section_option_from_id(setting_id) - tiny_config = rose.config.ConfigNode() + tiny_config = metomi.rose.config.ConfigNode() tiny_config.set([section, option], value) - tiny_meta_config = rose.config.ConfigNode() + tiny_meta_config = metomi.rose.config.ConfigNode() check_failed = self.evaluator.evaluate_rule( rule, setting_id, tiny_config, tiny_meta_config) if len(self._evaluated_rule_checks) > self.MAX_STORED_RULE_CHECKS: diff --git a/lib/python/rose/macros/value.py b/metomi/rose/macros/value.py similarity index 81% rename from lib/python/rose/macros/value.py rename to metomi/rose/macros/value.py index df638a87e3..c53218c2c8 100644 --- a/lib/python/rose/macros/value.py +++ b/metomi/rose/macros/value.py @@ -21,30 +21,30 @@ import copy import re -import rose.env -import rose.macro -import rose.macros.rule -import rose.variable +import metomi.rose.env +import metomi.rose.macro +import metomi.rose.macros.rule +import metomi.rose.variable -import rose.meta_type +import metomi.rose.meta_type REC_CHARACTER = re.compile(r"'(?:[^']|'')*'$") -class ValueChecker(rose.macro.MacroBase): +class ValueChecker(metomi.rose.macro.MacroBase): """Returns sections and options with wrong values according to metadata. It can use any metadata widget errors (within the GUI), or some defined behaviours (default) to check if a value is outside the allowed range, or the wrong type or composition. Call with either - rose.config objects or config filenames. + metomi.rose.config objects or config filenames. """ - META_PROPS = [rose.META_PROP_LENGTH, rose.META_PROP_PATTERN, - rose.META_PROP_RANGE, rose.META_PROP_TYPE, - rose.META_PROP_VALUES] + META_PROPS = [metomi.rose.META_PROP_LENGTH, metomi.rose.META_PROP_PATTERN, + metomi.rose.META_PROP_RANGE, metomi.rose.META_PROP_TYPE, + metomi.rose.META_PROP_VALUES] WARNING_BAD_PATTERN = "Value {0} does not contain the pattern: {1}" WARNING_BAD_RANGE = "Value {0} is not in the range criteria: {1}" WARNING_INVALID_LENGTH = 'Derived type has an invalid length: {0}' @@ -69,7 +69,7 @@ def validate(self, config, meta_config=None, _variables=None): sect, key = node_keys value = node.value # Skip environment variable values - if rose.env.contains_env_var(value): + if metomi.rose.env.contains_env_var(value): continue var_id = self._get_id_from_section_option(sect, key) self._validate_id(var_id, value, meta_config) @@ -82,7 +82,7 @@ def validate_variables(self, variables, meta_config): # Don't check ignored variables. if variable.ignored_reason: continue - if rose.env.contains_env_var(variable.value): + if metomi.rose.env.contains_env_var(variable.value): continue var_id = variable.metadata["id"] value = variable.value @@ -91,8 +91,8 @@ def validate_variables(self, variables, meta_config): def _validate_id(self, var_id, value, meta_config): """Validate the value of a particular variable id.""" - metadata = rose.macro.get_metadata_for_config_id(var_id, - meta_config) + metadata = metomi.rose.macro.get_metadata_for_config_id( + var_id, meta_config) sect, key = self._get_section_option_from_id(var_id) saved_metadata = copy.deepcopy(metadata) saved_metadata.pop('id') @@ -106,14 +106,14 @@ def _validate_id(self, var_id, value, meta_config): self.add_report(sect, key, value, self.bad_value_meta_map[goodness_id]) return - variable = rose.variable.Variable(key, value, metadata) + variable = metomi.rose.variable.Variable(key, value, metadata) metadata = variable.metadata if not isinstance(value, str): text = self.WARNING_NOT_STRING.format(repr(value)) self.bad_value_meta_map[goodness_id] = text self.add_report(sect, key, value, text) return - num_elements = variable.metadata.get(rose.META_PROP_LENGTH, 1) + num_elements = variable.metadata.get(metomi.rose.META_PROP_LENGTH, 1) if num_elements != 1: if num_elements == ':': num_elements = -1 @@ -123,11 +123,11 @@ def _validate_id(self, var_id, value, meta_config): except (TypeError, ValueError): num_elements = 1 val_list = [value] - type_list = metadata.get(rose.META_PROP_TYPE, '') + type_list = metadata.get(metomi.rose.META_PROP_TYPE, '') if isinstance(type_list, str): type_list = [type_list] if num_elements != 1: - val_list = rose.variable.array_split(value) + val_list = metomi.rose.variable.array_split(value) if num_elements != -1: if len(val_list) > num_elements * len(type_list): text = self.WARNING_WRONG_LENGTH.format( @@ -141,10 +141,10 @@ def _validate_id(self, var_id, value, meta_config): self.add_report(sect, key, value, text) return num_elements = len(val_list) - skip_nulls = (metadata.get(rose.META_PROP_COMPULSORY) != - rose.META_PROP_VALUE_TRUE and num_elements != 1) - if rose.META_PROP_VALUES in metadata: - meta_values = metadata[rose.META_PROP_VALUES] + skip_nulls = (metadata.get(metomi.rose.META_PROP_COMPULSORY) != + metomi.rose.META_PROP_VALUE_TRUE and num_elements != 1) + if metomi.rose.META_PROP_VALUES in metadata: + meta_values = metadata[metomi.rose.META_PROP_VALUES] for val in val_list: if skip_nulls and not val: continue @@ -158,8 +158,8 @@ def _validate_id(self, var_id, value, meta_config): self.bad_value_meta_map[goodness_id] = text self.add_report(sect, key, value, text) break - elif rose.META_PROP_TYPE in metadata: - meta_type = metadata[rose.META_PROP_TYPE] + elif metomi.rose.META_PROP_TYPE in metadata: + meta_type = metadata[metomi.rose.META_PROP_TYPE] if (num_elements == 1 and isinstance(meta_type, str)): # A standard, non array variable. for val in val_list: @@ -176,7 +176,7 @@ def _validate_id(self, var_id, value, meta_config): type_list = [meta_type] else: type_list = meta_type - val_list = rose.variable.array_split(value) + val_list = metomi.rose.variable.array_split(value) if num_elements == -1: array_length = len(val_list) / len(type_list) else: @@ -193,8 +193,8 @@ def _validate_id(self, var_id, value, meta_config): break except KeyError: pass - if rose.META_PROP_PATTERN in metadata: - pattern = metadata[rose.META_PROP_PATTERN] + if metomi.rose.META_PROP_PATTERN in metadata: + pattern = metadata[metomi.rose.META_PROP_PATTERN] if pattern not in self.pattern_comp_map: self.pattern_comp_map[pattern] = re.compile( pattern, re.VERBOSE) @@ -203,8 +203,8 @@ def _validate_id(self, var_id, value, meta_config): self.bad_value_meta_map[goodness_id] = text self.add_report(sect, key, value, text) return - if rose.META_PROP_RANGE in metadata: - range_pat = metadata[rose.META_PROP_RANGE] + if metomi.rose.META_PROP_RANGE in metadata: + range_pat = metadata[metomi.rose.META_PROP_RANGE] text = self.check_range(range_pat, var_id, sect, key, val_list, type_list, skip_nulls=skip_nulls) @@ -221,7 +221,7 @@ def _validate_id(self, var_id, value, meta_config): def meta_check(self, value, meta_type, sect, key): """Check function wrapper""" - res = rose.meta_type.meta_type_checker(value, meta_type) + res = metomi.rose.meta_type.meta_type_checker(value, meta_type) if not res[0]: self.add_report(sect, key, value, res[1]) return res[0] @@ -239,9 +239,9 @@ def check_range(self, range_pat, var_id, sect, key, val_list, type_list, """Check against a range pattern.""" is_range_complex = "this" in range_pat if is_range_complex: - tiny_config = rose.config.ConfigNode() + tiny_config = metomi.rose.config.ConfigNode() elif range_pat not in self.range_func_map: - check_func = rose.variable.parse_range_expression(range_pat) + check_func = metomi.rose.variable.parse_range_expression(range_pat) self.range_func_map.update({range_pat: check_func}) else: check_func = self.range_func_map[range_pat] @@ -260,12 +260,12 @@ def check_range(self, range_pat, var_id, sect, key, val_list, type_list, break if is_range_complex: tiny_config.set([sect, key], val) - tiny_meta_config = rose.config.ConfigNode() - evaluator = rose.macros.rule.RuleEvaluator() + tiny_meta_config = metomi.rose.config.ConfigNode() + evaluator = metomi.rose.macros.rule.RuleEvaluator() try: check_ok = evaluator.evaluate_rule( range_pat, var_id, tiny_config, tiny_meta_config) - except rose.macros.rule.RuleValueError: + except metomi.rose.macros.rule.RuleValueError: pass else: val_num = float(val) @@ -277,7 +277,7 @@ def check_range(self, range_pat, var_id, sect, key, val_list, type_list, return self.WARNING_BAD_RANGE.format(cur_val, range_pat) -class TypeFixer(rose.macro.MacroBase): +class TypeFixer(metomi.rose.macro.MacroBase): """Fix incorrect types.""" @@ -296,22 +296,22 @@ def transform(self, config, meta_config=None): sect = item.section opt = item.option var_id = self._get_id_from_section_option(sect, opt) - metadata = rose.macro.get_metadata_for_config_id(var_id, - meta_config) + metadata = metomi.rose.macro.get_metadata_for_config_id( + var_id, meta_config) saved_metadata = copy.deepcopy(metadata) saved_metadata.pop('id') node = config.get([sect, opt]) value = node.value ignored_state = node.state old_value = value - m_type = metadata.get(rose.META_PROP_TYPE) + m_type = metadata.get(metomi.rose.META_PROP_TYPE) if m_type in ["boolean", "character", "logical", "quoted"]: - if (metadata.get(rose.META_PROP_LENGTH, '').isdigit() or - metadata.get(rose.META_PROP_LENGTH) == ':'): - val_list = rose.variable.array_split(value) + if (metadata.get(metomi.rose.META_PROP_LENGTH, '').isdigit() or + metadata.get(metomi.rose.META_PROP_LENGTH) == ':'): + val_list = metomi.rose.variable.array_split(value) for i, val in enumerate(val_list): val_list[i] = self.meta_transform(val, m_type) - value = rose.variable.array_join(val_list) + value = metomi.rose.variable.array_join(val_list) else: value = self.meta_transform(value, m_type) if value != old_value: @@ -321,4 +321,4 @@ def transform(self, config, meta_config=None): return config, self.reports def meta_transform(self, value, meta_type): - return rose.meta_type.meta_type_transform(value, meta_type) + return metomi.rose.meta_type.meta_type_transform(value, meta_type) diff --git a/lib/python/rose/meta_type.py b/metomi/rose/meta_type.py similarity index 86% rename from lib/python/rose/meta_type.py rename to metomi/rose/meta_type.py index 3516c7149f..0a588aa263 100644 --- a/lib/python/rose/meta_type.py +++ b/metomi/rose/meta_type.py @@ -21,7 +21,7 @@ import ast import inspect import re -import rose.variable +import metomi.rose.variable REC_CHARACTER = re.compile(r"'(?:[^']|'')*'$") @@ -51,17 +51,17 @@ class BooleanMetaType(MetaType): WARNING = "Not true/false: {0}" def is_valid(self, value): - if value in [rose.TYPE_BOOLEAN_VALUE_TRUE, - rose.TYPE_BOOLEAN_VALUE_FALSE]: + if value in [metomi.rose.TYPE_BOOLEAN_VALUE_TRUE, + metomi.rose.TYPE_BOOLEAN_VALUE_FALSE]: return [True, None] else: return [False, self.WARNING.format(repr(value))] def transform(self, value): - if value.upper() == rose.TYPE_BOOLEAN_VALUE_FALSE.upper(): - return rose.TYPE_BOOLEAN_VALUE_FALSE - if value.upper() == rose.TYPE_BOOLEAN_VALUE_TRUE.upper(): - return rose.TYPE_BOOLEAN_VALUE_TRUE + if value.upper() == metomi.rose.TYPE_BOOLEAN_VALUE_FALSE.upper(): + return metomi.rose.TYPE_BOOLEAN_VALUE_FALSE + if value.upper() == metomi.rose.TYPE_BOOLEAN_VALUE_TRUE.upper(): + return metomi.rose.TYPE_BOOLEAN_VALUE_TRUE return value @@ -106,8 +106,8 @@ class PythonBooleanMetaType(MetaType): WARNING = "Not a valid Python boolean format (True/False): {0}" def is_valid(self, value): - if value not in [rose.TYPE_PYTHON_BOOLEAN_VALUE_TRUE, - rose.TYPE_PYTHON_BOOLEAN_VALUE_FALSE]: + if value not in [metomi.rose.TYPE_PYTHON_BOOLEAN_VALUE_TRUE, + metomi.rose.TYPE_PYTHON_BOOLEAN_VALUE_FALSE]: return [False, self.WARNING.format(repr(value))] return [True, None] @@ -148,18 +148,18 @@ class LogicalMetaType(MetaType): WARNING = "Not Fortran true/false: {0}" def is_valid(self, value): - if value not in [rose.TYPE_LOGICAL_VALUE_TRUE, - rose.TYPE_LOGICAL_VALUE_FALSE]: + if value not in [metomi.rose.TYPE_LOGICAL_VALUE_TRUE, + metomi.rose.TYPE_LOGICAL_VALUE_FALSE]: return [False, self.WARNING.format(repr(value))] return [True, None] def transform(self, value): if (value.upper() == '.F.' or - value.upper() == rose.TYPE_LOGICAL_VALUE_FALSE.upper()): - return rose.TYPE_LOGICAL_VALUE_FALSE + value.upper() == metomi.rose.TYPE_LOGICAL_VALUE_FALSE.upper()): + return metomi.rose.TYPE_LOGICAL_VALUE_FALSE if (value.upper() == '.T.' or - value.upper() == rose.TYPE_LOGICAL_VALUE_TRUE.upper()): - return rose.TYPE_LOGICAL_VALUE_TRUE + value.upper() == metomi.rose.TYPE_LOGICAL_VALUE_TRUE.upper()): + return metomi.rose.TYPE_LOGICAL_VALUE_TRUE return value diff --git a/lib/python/rose/metadata_check.py b/metomi/rose/metadata_check.py similarity index 66% rename from lib/python/rose/metadata_check.py rename to metomi/rose/metadata_check.py index 4731775e91..c620908a5f 100644 --- a/lib/python/rose/metadata_check.py +++ b/metomi/rose/metadata_check.py @@ -24,14 +24,14 @@ import sys from functools import cmp_to_key, partial -import rose.config -import rose.config_tree -import rose.formats.namelist -import rose.macro -import rose.macros -import rose.opt_parse -import rose.reporter -import rose.resource +import metomi.rose.config +import metomi.rose.config_tree +import metomi.rose.formats.namelist +import metomi.rose.macro +import metomi.rose.macros +import metomi.rose.opt_parse +import metomi.rose.reporter +import metomi.rose.resource ERROR_LOAD_META_CONFIG_DIR = "{0}: not a configuration metadata directory." @@ -50,40 +50,40 @@ def get_allowed_metadata_properties(): """Return a list of allowed properties such as type or values.""" properties = [] - for key in dir(rose): + for key in dir(metomi.rose): if (key.startswith("META_PROP_") and key not in ["META_PROP_VALUE_TRUE", "META_PROP_VALUE_FALSE"]): - properties.append(getattr(rose, key)) + properties.append(getattr(metomi.rose, key)) return properties def _check_compulsory(value): - allowed_values = [rose.META_PROP_VALUE_TRUE, - rose.META_PROP_VALUE_FALSE] + allowed_values = [metomi.rose.META_PROP_VALUE_TRUE, + metomi.rose.META_PROP_VALUE_FALSE] if value not in allowed_values: return INVALID_SYNTAX.format(value) def _check_copy_mode(value): """Check that the value for copy-mode is allowed.""" - if value not in [rose.COPY_MODE_NEVER, rose.COPY_MODE_CLEAR]: + if value not in [metomi.rose.COPY_MODE_NEVER, metomi.rose.COPY_MODE_CLEAR]: return INVALID_SYNTAX.format(value) def _check_duplicate(value): - allowed_values = [rose.META_PROP_VALUE_TRUE, - rose.META_PROP_VALUE_FALSE] + allowed_values = [metomi.rose.META_PROP_VALUE_TRUE, + metomi.rose.META_PROP_VALUE_FALSE] if value not in allowed_values: return INVALID_SYNTAX.format(value) def _check_rule(value, setting_id, meta_config): - evaluator = rose.macros.rule.RuleEvaluator() + evaluator = metomi.rose.macros.rule.RuleEvaluator() ids_used = evaluator.evaluate_rule_id_usage( value, setting_id, meta_config) ids_not_found = [] for id_ in sorted(ids_used): - id_to_find = rose.macro.REC_ID_STRIP.sub("", id_) + id_to_find = metomi.rose.macro.REC_ID_STRIP.sub("", id_) node = meta_config.get([id_to_find], no_ignore=True) if node is None: ids_not_found.append(id_to_find) @@ -102,17 +102,17 @@ def _check_macro(value, module_files=None, meta_dir=None): if not module_files: return try: - macros = rose.variable.array_split(value, only_this_delim=",") + macros = metomi.rose.variable.array_split(value, only_this_delim=",") except Exception as exc: return INVALID_SYNTAX.format(exc) for macro in macros: macro_name = macro method = None - if (macro.endswith("." + rose.macro.VALIDATE_METHOD) or - macro.endswith("." + rose.macro.TRANSFORM_METHOD)): + if (macro.endswith("." + metomi.rose.macro.VALIDATE_METHOD) or + macro.endswith("." + metomi.rose.macro.TRANSFORM_METHOD)): macro_name, method = macro.rsplit(".", 1) try: - macro_obj = rose.resource.import_object( + macro_obj = metomi.rose.resource.import_object( macro_name, module_files, _import_err_handler) except Exception as exc: return INVALID_IMPORT.format(macro, type(exc).__name__, exc) @@ -134,22 +134,22 @@ def _check_pattern(value): def _check_range(value): is_range_complex = "this" in value if is_range_complex: - test_config = rose.config.ConfigNode() + test_config = metomi.rose.config.ConfigNode() test_id = "env=A" test_config.set(["env", "A"], "0") - test_meta_config = rose.config.ConfigNode() - evaluator = rose.macros.rule.RuleEvaluator() + test_meta_config = metomi.rose.config.ConfigNode() + evaluator = metomi.rose.macros.rule.RuleEvaluator() try: evaluator.evaluate_rule( value, test_id, test_config, test_meta_config) - except rose.macros.rule.RuleValueError as exc: + except metomi.rose.macros.rule.RuleValueError as exc: return INVALID_RANGE_RULE_IDS.format(exc) except Exception as exc: return INVALID_SYNTAX.format(exc) else: try: - rose.variable.parse_range_expression(value) - except rose.variable.RangeSyntaxError as exc: + metomi.rose.variable.parse_range_expression(value) + except metomi.rose.variable.RangeSyntaxError as exc: return str(exc) except Exception as exc: return INVALID_SYNTAX.format(type(exc).__name__ + ": " + str(exc)) @@ -157,28 +157,30 @@ def _check_range(value): def _check_value_titles(title_value, values_value): try: - title_list = rose.variable.array_split(title_value, - only_this_delim=",") + title_list = metomi.rose.variable.array_split( + title_value, + only_this_delim=",") except Exception as exc: return INVALID_SYNTAX.format(type(exc).__name__ + ": " + str(exc)) try: - value_list = rose.variable.array_split(values_value, - only_this_delim=",") + value_list = metomi.rose.variable.array_split( + values_value, + only_this_delim=",") except Exception: - return INCOMPATIBLE.format(rose.META_PROP_VALUES) + return INCOMPATIBLE.format(metomi.rose.META_PROP_VALUES) if len(title_list) != len(value_list): - return INCOMPATIBLE.format(rose.META_PROP_VALUES) + return INCOMPATIBLE.format(metomi.rose.META_PROP_VALUES) def _check_type(value): - types = rose.variable.parse_type_expression(value) + types = metomi.rose.variable.parse_type_expression(value) if isinstance(types, str): types = [types] if " " in value and "," not in value: types = [value] bad_types = [] for type_ in types: - if type_ not in rose.TYPE_VALUES: + if type_ not in metomi.rose.TYPE_VALUES: bad_types.append(type_) if bad_types: return UNKNOWN_TYPE.format(VALUE_JOIN.join(bad_types)) @@ -186,7 +188,7 @@ def _check_type(value): def _check_values(value): try: - val_list = rose.variable.array_split(value, only_this_delim=",") + val_list = metomi.rose.variable.array_split(value, only_this_delim=",") except Exception as exc: return INVALID_SYNTAX.format(type(exc).__name__ + ": " + str(exc)) if not val_list: @@ -196,12 +198,13 @@ def _check_values(value): def _check_value_hints(hints_value): """Checks that the input is a valid format""" try: - hints_list = rose.variable.array_split(hints_value, - only_this_delim=",") + hints_list = metomi.rose.variable.array_split( + hints_value, + only_this_delim=",") except Exception as exc: return INVALID_SYNTAX.format(type(exc).__name__ + ": " + str(exc)) if not hints_list: - return INCOMPATIBLE.format(rose.META_PROP_VALUES) + return INCOMPATIBLE.format(metomi.rose.META_PROP_VALUES) def _check_widget(value, module_files=None, meta_dir=None): @@ -213,7 +216,7 @@ def _check_widget(value, module_files=None, meta_dir=None): return widget_name = value.split()[0] try: - widget = rose.resource.import_object( + widget = metomi.rose.resource.import_object( widget_name, module_files, _import_err_handler) except Exception as exc: return INVALID_IMPORT.format(widget_name, type(exc).__name__, exc) @@ -245,7 +248,7 @@ def metadata_check(meta_config, meta_dir=None, reports = [] module_files = _get_module_files(meta_dir) sections = list(meta_config.value) - sections.sort(key=cmp_to_key(rose.config.sort_settings)) + sections.sort(key=cmp_to_key(metomi.rose.config.sort_settings)) for section in sections: node = meta_config.value[section] if node.is_ignored() or not isinstance(node.value, dict): @@ -253,24 +256,25 @@ def metadata_check(meta_config, meta_dir=None, if (only_these_sections is not None and section not in only_these_sections): continue - if node.get([rose.META_PROP_VALUES], no_ignore=True) is not None: + if node.get( + [metomi.rose.META_PROP_VALUES], no_ignore=True) is not None: # 'values' supercedes other type-like props, so don't use them. - for type_like_prop in [rose.META_PROP_PATTERN, - rose.META_PROP_RANGE, - rose.META_PROP_TYPE]: + for type_like_prop in [metomi.rose.META_PROP_PATTERN, + metomi.rose.META_PROP_RANGE, + metomi.rose.META_PROP_TYPE]: if node.get([type_like_prop], no_ignore=True) is not None: info = UNNECESSARY_VALUES_PROP value = node.get([type_like_prop]).value - reports.append(rose.macro.MacroReport( + reports.append(metomi.rose.macro.MacroReport( section, type_like_prop, value, info)) - if node.get_value([rose.META_PROP_TYPE]) == "python_list": - if node.get_value([rose.META_PROP_LENGTH]): - info = INCOMPATIBLE.format(rose.META_PROP_TYPE) - value = node.get_value([rose.META_PROP_LENGTH]) - reports.append(rose.macro.MacroReport( - section, rose.META_PROP_LENGTH, value, info)) + if node.get_value([metomi.rose.META_PROP_TYPE]) == "python_list": + if node.get_value([metomi.rose.META_PROP_LENGTH]): + info = INCOMPATIBLE.format(metomi.rose.META_PROP_TYPE) + value = node.get_value([metomi.rose.META_PROP_LENGTH]) + reports.append(metomi.rose.macro.MacroReport( + section, metomi.rose.META_PROP_LENGTH, value, info)) options = list(node.value) - options.sort(key=cmp_to_key(rose.config.sort_settings)) + options.sort(key=cmp_to_key(metomi.rose.config.sort_settings)) for option in options: opt_node = node.value[option] if ((only_these_properties is not None and @@ -279,29 +283,33 @@ def metadata_check(meta_config, meta_dir=None, continue value = opt_node.value if (option not in allowed_properties and - not option.startswith(rose.META_PROP_WIDGET)): + not option.startswith(metomi.rose.META_PROP_WIDGET)): info = UNKNOWN_PROP.format(option) - reports.append(rose.macro.MacroReport(section, option, - value, info)) + reports.append(metomi.rose.macro.MacroReport( + section, option, value, info)) if section.split('=')[0] == 'ns': - allowed = [rose.META_PROP_TITLE, rose.META_PROP_DESCRIPTION, - rose.META_PROP_HELP, rose.META_PROP_SORT_KEY, - rose.META_PROP_MACRO, rose.META_PROP_URL, - rose.META_PROP_WIDGET] + allowed = [metomi.rose.META_PROP_TITLE, + metomi.rose.META_PROP_DESCRIPTION, + metomi.rose.META_PROP_HELP, + metomi.rose.META_PROP_SORT_KEY, + metomi.rose.META_PROP_MACRO, + metomi.rose.META_PROP_URL, + metomi.rose.META_PROP_WIDGET] if option not in allowed: info = INVALID_SETTING_FOR_NAMESPACE.format(option) - reports.append(rose.macro.MacroReport(section, option, - value, info)) - if option.startswith(rose.META_PROP_WIDGET): + reports.append(metomi.rose.macro.MacroReport( + section, option, value, info)) + if option.startswith(metomi.rose.META_PROP_WIDGET): check_func = partial(_check_widget, module_files=module_files) - elif option == rose.META_PROP_MACRO: + elif option == metomi.rose.META_PROP_MACRO: check_func = partial(_check_macro, module_files=module_files) - elif option == rose.META_PROP_VALUE_TITLES: + elif option == metomi.rose.META_PROP_VALUE_TITLES: check_func = partial( _check_value_titles, - values_value=node.get_value([rose.META_PROP_VALUES]) + values_value=node.get_value([metomi.rose.META_PROP_VALUES]) ) - elif option in [rose.META_PROP_FAIL_IF, rose.META_PROP_WARN_IF]: + elif option in [metomi.rose.META_PROP_FAIL_IF, + metomi.rose.META_PROP_WARN_IF]: check_func = partial( _check_rule, setting_id=section, meta_config=meta_config) else: @@ -309,34 +317,34 @@ def metadata_check(meta_config, meta_dir=None, check_func = globals().get(func_name, lambda v: None) info = check_func(value) if info: - reports.append(rose.macro.MacroReport(section, option, - value, info)) + reports.append(metomi.rose.macro.MacroReport( + section, option, value, info)) # Check triggering. - trigger_macro = rose.macros.trigger.TriggerMacro() + trigger_macro = metomi.rose.macros.trigger.TriggerMacro() # The .validate method will be replaced in a forthcoming enhancement. - trig_reports = trigger_macro.validate(rose.config.ConfigNode(), + trig_reports = trigger_macro.validate(metomi.rose.config.ConfigNode(), meta_config=meta_config) for report in trig_reports: if report.option is None: new_rep_section = report.section else: - new_rep_section = (report.section + rose.CONFIG_DELIMITER + + new_rep_section = (report.section + metomi.rose.CONFIG_DELIMITER + report.option) rep_id_node = meta_config.get([new_rep_section], no_ignore=True) if rep_id_node is None: new_rep_option = None new_rep_value = None else: - new_rep_option = rose.META_PROP_TRIGGER + new_rep_option = metomi.rose.META_PROP_TRIGGER rep_trig_node = meta_config.get([new_rep_section, new_rep_option], no_ignore=True) if rep_trig_node is None: new_rep_value = None else: new_rep_value = rep_trig_node.value - reports.append(rose.macro.MacroReport(new_rep_section, new_rep_option, - new_rep_value, report.info)) - reports.sort(key=cmp_to_key(rose.macro.report_sort)) + reports.append(metomi.rose.macro.MacroReport( + new_rep_section, new_rep_option, new_rep_value, report.info)) + reports.sort(key=cmp_to_key(metomi.rose.macro.report_sort)) return reports @@ -347,18 +355,18 @@ def _import_err_handler(exception): def main(): - opt_parser = rose.opt_parse.RoseOptionParser() + opt_parser = metomi.rose.opt_parse.RoseOptionParser() opt_parser.add_my_options("conf_dir", "property") - rose.macro.add_meta_paths() + metomi.rose.macro.add_meta_paths() opts, args = opt_parser.parse_args() - reporter = rose.reporter.Reporter(opts.verbosity - opts.quietness) + reporter = metomi.rose.reporter.Reporter(opts.verbosity - opts.quietness) if opts.conf_dir is None: opts.conf_dir = os.getcwd() opts.conf_dir = os.path.abspath(opts.conf_dir) try: - meta_config = rose.config_tree.ConfigTreeLoader().load( + meta_config = metomi.rose.config_tree.ConfigTreeLoader().load( opts.conf_dir, - rose.META_CONFIG_NAME, + metomi.rose.META_CONFIG_NAME, list(sys.path) ).node except IOError: @@ -373,15 +381,15 @@ def main(): meta_dir=opts.conf_dir, only_these_sections=sections, only_these_properties=properties) - macro_id = rose.macro.MACRO_OUTPUT_ID.format( - rose.macro.VALIDATE_METHOD.upper()[0], + macro_id = metomi.rose.macro.MACRO_OUTPUT_ID.format( + metomi.rose.macro.VALIDATE_METHOD.upper()[0], "rose.metadata_check.MetadataChecker") reports_map = {None: reports} - text = rose.macro.get_reports_as_text(reports_map, macro_id) + text = metomi.rose.macro.get_reports_as_text(reports_map, macro_id) if reports: reporter(text, kind=reporter.KIND_ERR, level=reporter.FAIL, prefix="") sys.exit(1) - reporter(rose.macro.MacroFinishNothingEvent(), level=reporter.V) + reporter(metomi.rose.macro.MacroFinishNothingEvent(), level=reporter.V) if __name__ == "__main__": diff --git a/lib/python/rose/metadata_gen.py b/metomi/rose/metadata_gen.py similarity index 70% rename from lib/python/rose/metadata_gen.py rename to metomi/rose/metadata_gen.py index 3d8bae2f51..608a9e20d7 100644 --- a/lib/python/rose/metadata_gen.py +++ b/metomi/rose/metadata_gen.py @@ -24,53 +24,55 @@ import os import sys -import rose.config -import rose.config_tree -import rose.formats.namelist -import rose.macro -import rose.macros.value -import rose.meta_type -import rose.opt_parse -import rose.resource +import metomi.rose.config +import metomi.rose.config_tree +import metomi.rose.formats.namelist +import metomi.rose.macro +import metomi.rose.macros.value +import metomi.rose.meta_type +import metomi.rose.opt_parse +import metomi.rose.resource def metadata_gen(config, meta_config=None, auto_type=False, prop_map=None): """Automatically guess the metadata for an application configuration.""" if prop_map is None: prop_map = {} - rose.macro.standard_format_config(config) + metomi.rose.macro.standard_format_config(config) if meta_config is None: - meta_config = rose.config.ConfigNode() + meta_config = metomi.rose.config.ConfigNode() for keylist, node in config.walk(): sect = keylist[0] if len(keylist) == 1: option = None else: option = keylist[1] - if sect in [rose.CONFIG_SECT_CMD]: + if sect in [metomi.rose.CONFIG_SECT_CMD]: continue - if keylist == [rose.CONFIG_SECT_TOP, rose.CONFIG_OPT_META_TYPE]: + if keylist == [metomi.rose.CONFIG_SECT_TOP, + metomi.rose.CONFIG_OPT_META_TYPE]: continue - meta_sect = rose.macro.REC_ID_STRIP.sub("", sect) - modifier_sect = rose.macro.REC_ID_STRIP_DUPL.sub("", sect) + meta_sect = metomi.rose.macro.REC_ID_STRIP.sub("", sect) + modifier_sect = metomi.rose.macro.REC_ID_STRIP_DUPL.sub("", sect) if sect and option is None: if (modifier_sect != meta_sect and modifier_sect != sect and meta_config.get([modifier_sect]) is None and auto_type): - meta_config.set([modifier_sect, rose.META_PROP_DUPLICATE], - rose.META_PROP_VALUE_TRUE) + meta_config.set( + [modifier_sect, metomi.rose.META_PROP_DUPLICATE], + metomi.rose.META_PROP_VALUE_TRUE) if meta_config.get([meta_sect]) is not None: continue meta_config.set([meta_sect]) if meta_sect != sect and auto_type: # Add duplicate = true at base and modifier level (if needed). - meta_config.set([meta_sect, rose.META_PROP_DUPLICATE], - rose.META_PROP_VALUE_TRUE) + meta_config.set([meta_sect, metomi.rose.META_PROP_DUPLICATE], + metomi.rose.META_PROP_VALUE_TRUE) for prop_key, prop_value in prop_map.items(): meta_config.set([meta_sect, prop_key], prop_value) if option is None: continue - meta_key = rose.macro.REC_ID_STRIP_DUPL.sub('', option) + meta_key = metomi.rose.macro.REC_ID_STRIP_DUPL.sub('', option) meta_opt = meta_sect + "=" + meta_key if meta_config.get([meta_opt]) is not None: continue @@ -80,9 +82,11 @@ def metadata_gen(config, meta_config=None, auto_type=False, prop_map=None): if auto_type: opt_type, length = type_gen(node.value) if opt_type is not None: - meta_config.set([meta_opt, rose.META_PROP_TYPE], opt_type) + meta_config.set( + [meta_opt, metomi.rose.META_PROP_TYPE], opt_type) if int(length) > 1: - meta_config.set([meta_opt, rose.META_PROP_LENGTH], length) + meta_config.set( + [meta_opt, metomi.rose.META_PROP_LENGTH], length) return meta_config @@ -96,12 +100,12 @@ def type_gen(value): length = 0 if not value: return None, str(length) - for val in rose.variable.array_split(value): + for val in metomi.rose.variable.array_split(value): length += 1 val_meta_type = "raw" for meta_type in ["integer", "real", "quoted", "character", "logical", "boolean"]: - is_ok = rose.meta_type.meta_type_checker(val, meta_type)[0] + is_ok = metomi.rose.meta_type.meta_type_checker(val, meta_type)[0] if is_ok: val_meta_type = meta_type break @@ -124,10 +128,10 @@ def type_gen(value): def main(): - opt_parser = rose.opt_parse.RoseOptionParser() + opt_parser = metomi.rose.opt_parse.RoseOptionParser() opt_parser.add_my_options("auto_type", "conf_dir", "output_dir") opts, args = opt_parser.parse_args() - rose.macro.add_meta_paths() + metomi.rose.macro.add_meta_paths() if opts.conf_dir is None: opts.conf_dir = os.getcwd() opts.conf_dir = os.path.abspath(opts.conf_dir) @@ -138,18 +142,18 @@ def main(): prop_val_map.update({key: val}) else: prop_val_map.update({arg: ""}) - for filename in [rose.SUB_CONFIG_NAME, rose.TOP_CONFIG_NAME]: + for filename in [metomi.rose.SUB_CONFIG_NAME, metomi.rose.TOP_CONFIG_NAME]: path = os.path.join(opts.conf_dir, filename) if os.path.isfile(path): break else: sys.exit(opt_parser.get_usage()) - source_config = rose.config.load(path) - meta_dir = os.path.join(opts.conf_dir, rose.CONFIG_META_DIR) - metadata_config = rose.config.ConfigNode() + source_config = metomi.rose.config.load(path) + meta_dir = os.path.join(opts.conf_dir, metomi.rose.CONFIG_META_DIR) + metadata_config = metomi.rose.config.ConfigNode() try: - metadata_config = rose.config_tree.ConfigTreeLoader().load( - meta_dir, rose.META_CONFIG_NAME, list(sys.path)).node + metadata_config = metomi.rose.config_tree.ConfigTreeLoader().load( + meta_dir, metomi.rose.META_CONFIG_NAME, list(sys.path)).node except IOError: pass metadata_config = metadata_gen(source_config, @@ -162,8 +166,8 @@ def main(): dest = opts.output_dir if not os.path.isdir(dest): os.mkdir(dest) - dest_file = os.path.join(dest, rose.META_CONFIG_NAME) - rose.config.dump(metadata_config, dest_file) + dest_file = os.path.join(dest, metomi.rose.META_CONFIG_NAME) + metomi.rose.config.dump(metadata_config, dest_file) if __name__ == "__main__": diff --git a/lib/python/rose/metadata_graph.py b/metomi/rose/metadata_graph.py similarity index 80% rename from lib/python/rose/metadata_graph.py rename to metomi/rose/metadata_graph.py index 4c7c1e93eb..08be2dc768 100644 --- a/lib/python/rose/metadata_graph.py +++ b/metomi/rose/metadata_graph.py @@ -26,14 +26,14 @@ from functools import cmp_to_key -import rose.config -import rose.config_tree -import rose.external -import rose.macro -import rose.macros.trigger -import rose.opt_parse -import rose.reporter -import rose.resource +import metomi.rose.config +import metomi.rose.config_tree +import metomi.rose.external +import metomi.rose.macro +import metomi.rose.macros.trigger +import metomi.rose.opt_parse +import metomi.rose.reporter +import metomi.rose.resource COLOUR_ENABLED = "green" @@ -44,7 +44,7 @@ SHAPE_NODE_EXTERNAL = "rectangle" SHAPE_NODE_SECTION = "octagon" -STATE_NORMAL = rose.config.ConfigNode.STATE_NORMAL +STATE_NORMAL = metomi.rose.config.ConfigNode.STATE_NORMAL STYLE_ARROWHEAD_EMPTY = "empty" @@ -60,14 +60,14 @@ def get_node_state_attrs(config, section, option=None, allowed_sections=None): if allowed_sections is None: allowed_sections = [] config_section_node = config.get([section]) - id_ = rose.macro.get_id_from_section_option(section, option) + id_ = metomi.rose.macro.get_id_from_section_option(section, option) state = "" config_node = config.get([section, option]) node_attrs["color"] = COLOUR_IGNORED if (config_section_node is not None and config_section_node.state != STATE_NORMAL and option is not None): - state = rose.config.STATE_SECT_IGNORED + state = metomi.rose.config.STATE_SECT_IGNORED if config_node is None: node_attrs["color"] = COLOUR_MISSING elif config_node.state != STATE_NORMAL: @@ -92,7 +92,7 @@ def get_graph(config, meta_config, name, allowed_sections=None, if allowed_properties is None: allowed_properties = [] if err_reporter is None: - err_reporter = rose.reporter.Reporter() + err_reporter = metomi.rose.reporter.Reporter() graph = pygraphviz.AGraph(directed=True) graph.graph_attr["rankdir"] = "LR" graph.graph_attr["label"] = name @@ -106,27 +106,28 @@ def get_graph(config, meta_config, name, allowed_sections=None, def add_trigger_graph(graph, config, meta_config, err_reporter, allowed_sections=None): """Add trigger-related nodes and edges to the graph.""" - trigger = rose.macros.trigger.TriggerMacro() + trigger = metomi.rose.macros.trigger.TriggerMacro() bad_reports = trigger.validate_dependencies(config, meta_config) if bad_reports: - err_reporter(rose.macro.get_reports_as_text( + err_reporter(metomi.rose.macro.get_reports_as_text( bad_reports, "trigger.TriggerMacro")) return None ids = [] for keylist, node in meta_config.walk(no_ignore=True): id_ = keylist[0] - if (id_.startswith(rose.META_PROP_NS + rose.CONFIG_DELIMITER) or - id_.startswith(rose.SUB_CONFIG_FILE_DIR + ":*")): + if (id_.startswith( + metomi.rose.META_PROP_NS + metomi.rose.CONFIG_DELIMITER) or + id_.startswith(metomi.rose.SUB_CONFIG_FILE_DIR + ":*")): continue if isinstance(node.value, dict): section, option = ( - rose.macro.get_section_option_from_id(id_)) + metomi.rose.macro.get_section_option_from_id(id_)) if not allowed_sections or ( allowed_sections and section in allowed_sections): ids.append(id_) - ids.sort(key=cmp_to_key(rose.config.sort_settings)) + ids.sort(key=cmp_to_key(metomi.rose.config.sort_settings)) for id_ in ids: - section, option = rose.macro.get_section_option_from_id(id_) + section, option = metomi.rose.macro.get_section_option_from_id(id_) node_attrs = get_node_state_attrs( config, section, option, allowed_sections=allowed_sections @@ -134,7 +135,8 @@ def add_trigger_graph(graph, config, meta_config, err_reporter, graph.add_node(id_, **node_attrs) for setting_id, id_value_dict in sorted( trigger.trigger_family_lookup.items()): - section, option = rose.macro.get_section_option_from_id(setting_id) + section, option = metomi.rose.macro.get_section_option_from_id( + setting_id) section_node = config.get([section], no_ignore=True) node = config.get([section, option]) if node is None: @@ -143,8 +145,9 @@ def add_trigger_graph(graph, config, meta_config, err_reporter, setting_value = node.value setting_is_section_ignored = (option is None and section_node is None) for dependent_id, values in sorted(id_value_dict.items()): - dep_section, dep_option = rose.macro.get_section_option_from_id( - dependent_id) + dep_section, dep_option = \ + metomi.rose.macro.get_section_option_from_id( + dependent_id) if (allowed_sections and (section not in allowed_sections and dep_section not in allowed_sections)): @@ -224,20 +227,22 @@ def output_graph(graph, debug_mode=False, filename=None, form="svg"): print(image_file_handle.read().decode()) image_file_handle.close() return - rose.external.launch_image_viewer(image_file_handle.name, run_fg=True) + metomi.rose.external.launch_image_viewer( + image_file_handle.name, run_fg=True) def _exit_with_metadata_fail(): """Handle a load metadata failure.""" - text = rose.macro.ERROR_LOAD_METADATA.format("") - rose.reporter.Reporter()(text, - kind=rose.reporter.Reporter.KIND_ERR, - level=rose.reporter.Reporter.FAIL) + text = metomi.rose.macro.ERROR_LOAD_METADATA.format("") + metomi.rose.reporter.Reporter()( + text, + kind=metomi.rose.reporter.Reporter.KIND_ERR, + level=metomi.rose.reporter.Reporter.FAIL) sys.exit(1) def _load_override_config(): - conf = rose.resource.ResourceLocator.default().get_conf().get( + conf = metomi.rose.resource.ResourceLocator.default().get_conf().get( ["rose-metadata-graph"]) if conf is None: return @@ -254,8 +259,8 @@ def _load_override_config(): def main(): """Run the metadata graphing from the command line.""" _load_override_config() - rose.macro.add_meta_paths() - opt_parser = rose.opt_parse.RoseOptionParser() + metomi.rose.macro.add_meta_paths() + opt_parser = metomi.rose.opt_parse.RoseOptionParser() options = ["conf_dir", "meta_path", "output_dir", "property"] opt_parser.add_my_options(*options) opts, args = opt_parser.parse_args() @@ -263,28 +268,30 @@ def main(): os.chdir(opts.conf_dir) opts.conf_dir = os.getcwd() sys.path.append( - rose.resource.ResourceLocator.default().get_util_home()) - rose.macro.add_opt_meta_paths(opts.meta_path) + metomi.rose.resource.ResourceLocator.default().get_util_home()) + metomi.rose.macro.add_opt_meta_paths(opts.meta_path) - config_file_path = os.path.join(opts.conf_dir, rose.SUB_CONFIG_NAME) - meta_config_file_path = os.path.join(opts.conf_dir, rose.META_CONFIG_NAME) - config_tree_loader = rose.config_tree.ConfigTreeLoader() + config_file_path = os.path.join(opts.conf_dir, metomi.rose.SUB_CONFIG_NAME) + meta_config_file_path = os.path.join( + opts.conf_dir, metomi.rose.META_CONFIG_NAME) + config_tree_loader = metomi.rose.config_tree.ConfigTreeLoader() if os.path.exists(config_file_path): - config = config_tree_loader(opts.conf_dir, rose.SUB_CONFIG_NAME, + config = config_tree_loader(opts.conf_dir, metomi.rose.SUB_CONFIG_NAME, conf_dir_paths=sys.path).node - meta_path = rose.macro.load_meta_path(config, opts.conf_dir)[0] + meta_path = metomi.rose.macro.load_meta_path(config, opts.conf_dir)[0] if meta_path is None: _exit_with_metadata_fail() - meta_config = rose.macro.load_meta_config( + meta_config = metomi.rose.macro.load_meta_config( config, directory=opts.conf_dir, ) if not meta_config.value.keys(): _exit_with_metadata_fail() elif os.path.exists(meta_config_file_path): - config = rose.config.ConfigNode() + config = metomi.rose.config.ConfigNode() meta_config = ( - config_tree_loader(opts.conf_dir, rose.META_CONFIG_NAME)).node + config_tree_loader( + opts.conf_dir, metomi.rose.META_CONFIG_NAME)).node else: _exit_with_metadata_fail() name = opts.conf_dir diff --git a/lib/python/rose/namelist_dump.py b/metomi/rose/namelist_dump.py similarity index 91% rename from lib/python/rose/namelist_dump.py rename to metomi/rose/namelist_dump.py index 666747d6fd..7c7192d0db 100644 --- a/lib/python/rose/namelist_dump.py +++ b/metomi/rose/namelist_dump.py @@ -21,15 +21,15 @@ Module to convert from a Fortran namelist file to a Rose configuration. This contains wrapper functions for the namelist parser and the dumper -(rose.config). +(metomi.rose.config). """ import re import sys -import rose.config -import rose.formats.namelist -from rose.opt_parse import RoseOptionParser +import metomi.rose.config +import metomi.rose.formats.namelist +from metomi.rose.opt_parse import RoseOptionParser RE_NAME_INDEX = re.compile(r"^(.*)\((\d+)\)$") @@ -73,7 +73,7 @@ def namelist_dump(args=None, output_file=None, case_mode=None): output_file = open(output_file, "w") # Config: file: sections - config = rose.config.ConfigNode() + config = metomi.rose.config.ConfigNode() files = [] for arg in args: if arg == STD_FILE_ARG: @@ -84,7 +84,7 @@ def namelist_dump(args=None, output_file=None, case_mode=None): config.set(["file:" + arg], {}) # Parse files into a list of NamelistGroup objects - groups = rose.formats.namelist.parse(files) + groups = metomi.rose.formats.namelist.parse(files) # Count group in files and group groups_in_file = {} @@ -125,10 +125,11 @@ def namelist_dump(args=None, output_file=None, case_mode=None): config.set([section, lhs], obj.get_rhs_as_string()) # Config: write results - rose.config.dump(config, output_file, - sort_sections=_sort_config_key, - sort_option_items=_sort_config_key, - env_escape_ok=True) + metomi.rose.config.dump( + config, output_file, + sort_sections=_sort_config_key, + sort_option_items=_sort_config_key, + env_escape_ok=True) output_file.close() diff --git a/lib/python/rose/opt_parse.py b/metomi/rose/opt_parse.py similarity index 99% rename from lib/python/rose/opt_parse.py rename to metomi/rose/opt_parse.py index 21cf55e29e..a24e2b4e77 100644 --- a/lib/python/rose/opt_parse.py +++ b/metomi/rose/opt_parse.py @@ -20,7 +20,7 @@ """Common option parser for Rose command utilities.""" from optparse import OptionParser -from rose.resource import ResourceLocator +from metomi.rose.resource import ResourceLocator class RoseOptionParser(OptionParser): diff --git a/lib/python/rose/popen.py b/metomi/rose/popen.py similarity index 99% rename from lib/python/rose/popen.py rename to metomi/rose/popen.py index bdfbcf92f8..d2db45f004 100644 --- a/lib/python/rose/popen.py +++ b/metomi/rose/popen.py @@ -23,8 +23,8 @@ import asyncio import re import io -from rose.reporter import Event -from rose.resource import ResourceLocator +from metomi.rose.reporter import Event +from metomi.rose.resource import ResourceLocator import shlex from subprocess import Popen, PIPE import sys diff --git a/lib/python/rose/reporter.py b/metomi/rose/reporter.py similarity index 100% rename from lib/python/rose/reporter.py rename to metomi/rose/reporter.py diff --git a/lib/python/rose/resource.py b/metomi/rose/resource.py similarity index 95% rename from lib/python/rose/resource.py rename to metomi/rose/resource.py index 0aa65319db..4f7af6cfbe 100644 --- a/lib/python/rose/resource.py +++ b/metomi/rose/resource.py @@ -22,7 +22,7 @@ """ import os -from rose.config import ConfigLoader, ConfigNode +from metomi.rose.config import ConfigLoader, ConfigNode import inspect import string import sys @@ -33,16 +33,16 @@ def get_util_home(*args): - """Return ROSE_HOME or the dirname of the dirname of sys.argv[0]. + """Return ROSE_LIB or the dirname of the dirname of sys.argv[0]. If args are specified, they are added to the end of returned path. """ try: - value = os.environ["ROSE_HOME"] + value = os.environ["ROSE_LIB"] except KeyError: value = os.path.abspath(__file__) - for _ in range(4): # assume __file__ under $ROSE_HOME/lib/python/rose/ + for _ in range(3): # assume __file__ under $ROSE_LIB/metomi/rose/ value = os.path.dirname(value) return os.path.join(value, *args) @@ -148,7 +148,7 @@ def get_util_name(self, separator=" "): return os.path.basename(sys.argv[0]) def get_version(self, ignore_environment=False): - """return the current rose_version number. + """return the current metomi.rose_version number. By default pass through the value of the ``ROSE_VERSION`` environment variable. @@ -189,7 +189,7 @@ def import_object(import_string, from_files, error_handler, import_string is the '.' delimited path to the callable, as in normal Python - e.g. - rose.config_editor.pagewidget.table.PageTable + metomi.rose.config_editor.pagewidget.table.PageTable from_files is a list of available Python file paths to search in error_handler is a function that accepts an Exception instance or string and does something appropriate with it. diff --git a/lib/python/rose/run.py b/metomi/rose/run.py similarity index 94% rename from lib/python/rose/run.py rename to metomi/rose/run.py index 1fa5f363ea..22cde9e8d1 100644 --- a/lib/python/rose/run.py +++ b/metomi/rose/run.py @@ -20,12 +20,12 @@ """Shared utilities for app/suite/task run.""" import os -from rose.config_processor import ConfigProcessorsManager -from rose.config_tree import ConfigTreeLoader -from rose.fs_util import FileSystemUtil -from rose.popen import RosePopener -from rose.reporter import Event -from rose.suite_engine_proc import SuiteEngineProcessor +from metomi.rose.config_processor import ConfigProcessorsManager +from metomi.rose.config_tree import ConfigTreeLoader +from metomi.rose.fs_util import FileSystemUtil +from metomi.rose.popen import RosePopener +from metomi.rose.reporter import Event +from metomi.rose.suite_engine_proc import SuiteEngineProcessor import shlex import shutil from uuid import uuid4 @@ -129,7 +129,7 @@ def handle_event(self, *args, **kwargs): def config_load(self, opts): """Combine main config file with optional ones and defined ones. - Return an instance of rose.config_tree.ConfigTree. + Return an instance of metomi.rose.config_tree.ConfigTree. """ diff --git a/lib/python/rose/run_source_vc.py b/metomi/rose/run_source_vc.py similarity index 94% rename from lib/python/rose/run_source_vc.py rename to metomi/rose/run_source_vc.py index 011e928b80..be0a5f9566 100644 --- a/lib/python/rose/run_source_vc.py +++ b/metomi/rose/run_source_vc.py @@ -20,10 +20,10 @@ """Write version control information of sources used in run time.""" import os -from rose.popen import RosePopener +from metomi.rose.popen import RosePopener import sys import _io -from rose.unicode_utils import write_safely +from metomi.rose.unicode_utils import write_safely def write_source_vc_info(run_source_dir, output=None, popen=None): @@ -32,7 +32,7 @@ def write_source_vc_info(run_source_dir, output=None, popen=None): run_source_dir -- The source directory we are interested in. output -- An open file handle or a string containing a writable path. If not specified, use sys.stdout. - popen -- A rose.popen.RosePopener instance for running vc commands. + popen -- A metomi.rose.popen.RosePopener instance for running vc commands. If not specified, use a new local instance. """ diff --git a/lib/python/rose/scheme_handler.py b/metomi/rose/scheme_handler.py similarity index 100% rename from lib/python/rose/scheme_handler.py rename to metomi/rose/scheme_handler.py diff --git a/lib/python/rose/section.py b/metomi/rose/section.py similarity index 100% rename from lib/python/rose/section.py rename to metomi/rose/section.py diff --git a/lib/python/rose/stem.py b/metomi/rose/stem.py similarity index 94% rename from lib/python/rose/stem.py rename to metomi/rose/stem.py index 07691539b0..459db4e8a6 100644 --- a/lib/python/rose/stem.py +++ b/metomi/rose/stem.py @@ -23,14 +23,14 @@ import re import sys -import rose.config -from rose.fs_util import FileSystemUtil -from rose.host_select import HostSelector -from rose.opt_parse import RoseOptionParser -from rose.popen import RosePopener, RosePopenError -from rose.reporter import Reporter, Event -from rose.resource import ResourceLocator -from rose.suite_run import SuiteRunner +import metomi.rose.config +from metomi.rose.fs_util import FileSystemUtil +from metomi.rose.host_select import HostSelector +from metomi.rose.opt_parse import RoseOptionParser +from metomi.rose.popen import RosePopener, RosePopenError +from metomi.rose.reporter import Reporter, Event +from metomi.rose.resource import ResourceLocator +from metomi.rose.suite_run import SuiteRunner DEFAULT_TEST_DIR = 'rose-stem' OPTIONS = ['group', 'source', 'task', ] @@ -96,17 +96,17 @@ def __repr__(self): class RoseStemVersionException(Exception): - """Exception class when running the wrong rose-stem version.""" + """Exception class when running the wrong metomi.rose-stem version.""" def __init__(self, version): Exception.__init__(self, version) if version is None: - self.suite_version = "not rose-stem compatible" + self.suite_version = "not metomi.rose-stem compatible" else: self.suite_version = "at version %s" % (version) def __repr__(self): - return "Running rose-stem version %s but suite is %s" % ( + return "Running metomi.rose-stem version %s but suite is %s" % ( ROSE_STEM_VERSION, self.suite_version) __str__ = __repr__ @@ -114,7 +114,7 @@ def __repr__(self): class RoseSuiteConfNotFoundException(Exception): - """Exception class when unable to find rose-suite.conf.""" + """Exception class when unable to find metomi.rose-suite.conf.""" def __init__(self, location): Exception.__init__(self, location) @@ -350,15 +350,16 @@ def _this_suite(self): return suitedir def _read_auto_opts(self): - """Read the site rose.conf file.""" + """Read the site metomi.rose.conf file.""" return ResourceLocator.default().get_conf().get_value( ["rose-stem", "automatic-options"]) def _check_suite_version(self, fname): - """Check the suite is compatible with this version of rose-stem.""" + """Check the suite is compatible with this version of metomi.rose-stem. + """ if not os.path.isfile(fname): raise RoseSuiteConfNotFoundException(os.path.dirname(fname)) - config = rose.config.load(fname) + config = metomi.rose.config.load(fname) suite_rose_stem_version = config.get(['ROSE_STEM_VERSION']) if suite_rose_stem_version: suite_rose_stem_version = int(suite_rose_stem_version.value) @@ -456,7 +457,7 @@ def process(self): def main(): - """Launcher for command line invokation of rose stem.""" + """Launcher for command line invokation of metomi.rose stem.""" # Process options opt_parser = RoseOptionParser() diff --git a/lib/python/rose/suite_clean.py b/metomi/rose/suite_clean.py similarity index 95% rename from lib/python/rose/suite_clean.py rename to metomi/rose/suite_clean.py index b91e5272e8..7ef1de1da2 100644 --- a/lib/python/rose/suite_clean.py +++ b/metomi/rose/suite_clean.py @@ -21,13 +21,14 @@ import os from pipes import quote -from rose.config import ConfigLoader, ConfigNode, ConfigSyntaxError -from rose.fs_util import FileSystemEvent -from rose.host_select import HostSelector -from rose.opt_parse import RoseOptionParser -from rose.popen import RosePopenError -from rose.reporter import Reporter -from rose.suite_engine_proc import SuiteEngineProcessor, SuiteStillRunningError +from metomi.rose.config import ConfigLoader, ConfigNode, ConfigSyntaxError +from metomi.rose.fs_util import FileSystemEvent +from metomi.rose.host_select import HostSelector +from metomi.rose.opt_parse import RoseOptionParser +from metomi.rose.popen import RosePopenError +from metomi.rose.reporter import Reporter +from metomi.rose.suite_engine_proc import ( + SuiteEngineProcessor, SuiteStillRunningError) import sys import traceback from uuid import uuid4 diff --git a/lib/python/rose/suite_control.py b/metomi/rose/suite_control.py similarity index 95% rename from lib/python/rose/suite_control.py rename to metomi/rose/suite_control.py index cf25e9af83..6aeea0f55b 100644 --- a/lib/python/rose/suite_control.py +++ b/metomi/rose/suite_control.py @@ -20,10 +20,10 @@ """Launch suite engine's control commands.""" import os -from rose.fs_util import FileSystemUtil -from rose.opt_parse import RoseOptionParser -from rose.reporter import Reporter -from rose.suite_engine_proc import SuiteEngineProcessor +from metomi.rose.fs_util import FileSystemUtil +from metomi.rose.opt_parse import RoseOptionParser +from metomi.rose.reporter import Reporter +from metomi.rose.suite_engine_proc import SuiteEngineProcessor import sys diff --git a/lib/python/rose/suite_engine_proc.py b/metomi/rose/suite_engine_proc.py similarity index 96% rename from lib/python/rose/suite_engine_proc.py rename to metomi/rose/suite_engine_proc.py index 344a8d355e..feb67e1124 100644 --- a/lib/python/rose/suite_engine_proc.py +++ b/metomi/rose/suite_engine_proc.py @@ -25,13 +25,13 @@ import os import pwd import re -from rose.date import RoseDateTimeOperator, OffsetValueError -from rose.fs_util import FileSystemUtil -from rose.host_select import HostSelector -from rose.popen import RosePopener -from rose.reporter import Event -from rose.resource import ResourceLocator -from rose.scheme_handler import SchemeHandlersManager +from metomi.rose.date import RoseDateTimeOperator, OffsetValueError +from metomi.rose.fs_util import FileSystemUtil +from metomi.rose.host_select import HostSelector +from metomi.rose.popen import RosePopener +from metomi.rose.reporter import Event +from metomi.rose.resource import ResourceLocator +from metomi.rose.scheme_handler import SchemeHandlersManager import sys import webbrowser @@ -307,14 +307,15 @@ def get_processor(cls, key=None, event_handler=None, popen=None, if cls.SCHEME_HANDLER_MANAGER is None: path = os.path.dirname( - os.path.dirname(sys.modules["rose"].__file__)) + os.path.dirname(sys.modules["metomi.rose"].__file__)) cls.SCHEME_HANDLER_MANAGER = SchemeHandlersManager( [path], ns="rose.suite_engine_procs", attrs=["SCHEME"], can_handle=None, event_handler=event_handler, popen=popen, fs_util=fs_util, host_selector=host_selector) if key is None: key = cls.SCHEME_DEFAULT - return cls.SCHEME_HANDLER_MANAGER.get_handler(key) + x = cls.SCHEME_HANDLER_MANAGER.get_handler(key) + return x def __init__(self, event_handler=None, popen=None, fs_util=None, host_selector=None, **_): @@ -411,14 +412,16 @@ def get_suite_log_url(self, user_name, suite_name): break if not rose_bush_url: conf = ResourceLocator.default().get_conf() - rose_bush_url = conf.get_value(["rose-suite-log", "rose-bush"]) + rose_bush_url = conf.get_value( + ["rose-suite-log", "rose-bush"]) if not rose_bush_url: return "file://" + suite_d if not rose_bush_url.endswith("/"): rose_bush_url += "/" if not user_name: user_name = pwd.getpwuid(os.getuid()).pw_name - return rose_bush_url + "/".join(["taskjobs", user_name, suite_name]) + return rose_bush_url + "/".join( + ["taskjobs", user_name, suite_name]) def get_task_auth(self, suite_name, task_name): """Return [user@]host for a remote task in a suite.""" diff --git a/lib/python/rose/suite_engine_procs/__init__.py b/metomi/rose/suite_engine_procs/__init__.py similarity index 100% rename from lib/python/rose/suite_engine_procs/__init__.py rename to metomi/rose/suite_engine_procs/__init__.py diff --git a/lib/python/rose/suite_engine_procs/cylc.py b/metomi/rose/suite_engine_procs/cylc.py similarity index 99% rename from lib/python/rose/suite_engine_procs/cylc.py rename to metomi/rose/suite_engine_procs/cylc.py index 72647f29e9..b2175f9bb4 100644 --- a/lib/python/rose/suite_engine_procs/cylc.py +++ b/metomi/rose/suite_engine_procs/cylc.py @@ -32,10 +32,10 @@ from time import sleep from uuid import uuid4 -from rose.fs_util import FileSystemEvent -from rose.popen import RosePopenError -from rose.reporter import Event, Reporter -from rose.suite_engine_proc import ( +from metomi.rose.fs_util import FileSystemEvent +from metomi.rose.popen import RosePopenError +from metomi.rose.reporter import Event, Reporter +from metomi.rose.suite_engine_proc import ( SuiteEngineProcessor, SuiteEngineGlobalConfCompatError, SuiteNotRunningError, SuiteStillRunningError, TaskProps) diff --git a/lib/python/rose/suite_hook.py b/metomi/rose/suite_hook.py similarity index 95% rename from lib/python/rose/suite_hook.py rename to metomi/rose/suite_hook.py index 857fb6f5e5..22ce3e151d 100644 --- a/lib/python/rose/suite_hook.py +++ b/metomi/rose/suite_hook.py @@ -23,11 +23,11 @@ from email.mime.text import MIMEText import os import pwd -from rose.opt_parse import RoseOptionParser -from rose.popen import RosePopener -from rose.reporter import Reporter -from rose.resource import ResourceLocator -from rose.suite_engine_proc import SuiteEngineProcessor +from metomi.rose.opt_parse import RoseOptionParser +from metomi.rose.popen import RosePopener +from metomi.rose.reporter import Reporter +from metomi.rose.resource import ResourceLocator +from metomi.rose.suite_engine_proc import SuiteEngineProcessor from smtplib import SMTP, SMTPException import socket diff --git a/lib/python/rose/suite_log.py b/metomi/rose/suite_log.py similarity index 92% rename from lib/python/rose/suite_log.py rename to metomi/rose/suite_log.py index b722631bd2..5a38df9eac 100644 --- a/lib/python/rose/suite_log.py +++ b/metomi/rose/suite_log.py @@ -21,10 +21,10 @@ import os import pwd -from rose.opt_parse import RoseOptionParser -from rose.reporter import Event, Reporter -from rose.suite_engine_proc import SuiteEngineProcessor -from rose.suite_control import get_suite_name +from metomi.rose.opt_parse import RoseOptionParser +from metomi.rose.reporter import Event, Reporter +from metomi.rose.suite_engine_proc import SuiteEngineProcessor +from metomi.rose.suite_control import get_suite_name import sys from time import sleep import traceback @@ -80,7 +80,8 @@ def suite_log_view(opts, args, event_handler=None): url = suite_engine_proc.get_suite_log_url(opts.user, opts.name) if url.startswith("file://"): if (opts.non_interactive or - input("Start rose bush? [y/n] (default=n) ") == "y"): + input( + "Start rose bush? [y/n] (default=n) ") == "y"): suite_engine_proc.popen.run_bg( "rose", "bush", "start", preexec_fn=os.setpgrp) is_rose_bush_started = True diff --git a/lib/python/rose/suite_restart.py b/metomi/rose/suite_restart.py similarity index 91% rename from lib/python/rose/suite_restart.py rename to metomi/rose/suite_restart.py index c3ac27f531..a80654ef68 100644 --- a/lib/python/rose/suite_restart.py +++ b/metomi/rose/suite_restart.py @@ -23,11 +23,11 @@ import sys import traceback -from rose.opt_parse import RoseOptionParser -from rose.popen import RosePopenError -from rose.reporter import Reporter -from rose.suite_control import get_suite_name, SuiteNotFoundError -from rose.suite_engine_proc import SuiteEngineProcessor +from metomi.rose.opt_parse import RoseOptionParser +from metomi.rose.popen import RosePopenError +from metomi.rose.reporter import Reporter +from metomi.rose.suite_control import get_suite_name, SuiteNotFoundError +from metomi.rose.suite_engine_proc import SuiteEngineProcessor class SuiteRestarter(object): diff --git a/lib/python/rose/suite_run.py b/metomi/rose/suite_run.py similarity index 97% rename from lib/python/rose/suite_run.py rename to metomi/rose/suite_run.py index 8ce7ca7e32..1dc71ec605 100644 --- a/lib/python/rose/suite_run.py +++ b/metomi/rose/suite_run.py @@ -25,16 +25,16 @@ from glob import glob import os import pipes -from rose.config import ConfigDumper, ConfigLoader, ConfigNode -from rose.env import env_var_process -from rose.host_select import HostSelector -from rose.opt_parse import RoseOptionParser -from rose.popen import RosePopenError -from rose.reporter import Event, Reporter, ReporterContext -from rose.resource import ResourceLocator -from rose.run import ConfigValueError, NewModeError, Runner -from rose.run_source_vc import write_source_vc_info -from rose.suite_clean import SuiteRunCleaner +from metomi.rose.config import ConfigDumper, ConfigLoader, ConfigNode +from metomi.rose.env import env_var_process +from metomi.rose.host_select import HostSelector +from metomi.rose.opt_parse import RoseOptionParser +from metomi.rose.popen import RosePopenError +from metomi.rose.reporter import Event, Reporter, ReporterContext +from metomi.rose.resource import ResourceLocator +from metomi.rose.run import ConfigValueError, NewModeError, Runner +from metomi.rose.run_source_vc import write_source_vc_info +from metomi.rose.suite_clean import SuiteRunCleaner import shutil import sys from tempfile import TemporaryFile, mkdtemp @@ -260,8 +260,8 @@ def run_impl(self, opts, args, uuid, work_files): write_source_vc_info( suite_conf_dir, "log/" + prefix + ".version", self.popen) - # If run through rose-stem, install version information files for - # each source tree if they're a working copy + # If run through rose-stem, install version information + # files for each source tree if they're a working copy if hasattr(opts, 'source') and hasattr(opts, 'project'): for i, url in enumerate(opts.source): if os.path.isdir(url): @@ -373,7 +373,8 @@ def run_impl(self, opts, args, uuid, work_files): "remote-rose-bin", host=host, conf_tree=conf_tree, default="rose") # Build remote "rose suite-run" command - shcommand += " %s suite-run -vv -n %s" % (rose_bin, suite_name) + shcommand += " %s suite-run -vv -n %s" % ( + rose_bin, suite_name) for key in ["new", "debug", "install-only"]: attr = key.replace("-", "_") + "_mode" if getattr(opts, attr, None) is not None: diff --git a/lib/python/rose/task_env.py b/metomi/rose/task_env.py similarity index 94% rename from lib/python/rose/task_env.py rename to metomi/rose/task_env.py index 439f9b7dc3..6751ac2a14 100644 --- a/lib/python/rose/task_env.py +++ b/metomi/rose/task_env.py @@ -21,11 +21,11 @@ from glob import glob import os -from rose.env import EnvExportEvent -from rose.opt_parse import RoseOptionParser -from rose.reporter import Reporter -from rose.resource import ResourceLocator -from rose.suite_engine_proc import SuiteEngineProcessor +from metomi.rose.env import EnvExportEvent +from metomi.rose.opt_parse import RoseOptionParser +from metomi.rose.reporter import Reporter +from metomi.rose.resource import ResourceLocator +from metomi.rose.suite_engine_proc import SuiteEngineProcessor import sys import traceback @@ -38,8 +38,8 @@ def get_prepend_paths(event_handler=None, path_root=None, path_glob_args=None, full_mode=False): """Return map of PATH-like env-var names to path lists to prepend to them. - event_handler -- An instance of rose.reporter.Reporter or an object with a - similar interface. + event_handler -- An instance of metomi.rose.reporter.Reporter or an object + with a similar interface. path_root -- If a glob is relative and this is defined, this is the root directory of the relative path. path_glob_args -- A list of strings in the form GLOB or NAME=GLOB. NAME is diff --git a/lib/python/rose/task_run.py b/metomi/rose/task_run.py similarity index 94% rename from lib/python/rose/task_run.py rename to metomi/rose/task_run.py index cf8cd81ba2..8dc5f50653 100644 --- a/lib/python/rose/task_run.py +++ b/metomi/rose/task_run.py @@ -20,13 +20,13 @@ """Implement "rose task-run".""" import os -from rose.app_run import AppRunner -from rose.env import env_export -from rose.opt_parse import RoseOptionParser -from rose.popen import RosePopenError -from rose.reporter import Reporter -from rose.run import Runner -from rose.task_env import get_prepend_paths +from metomi.rose.app_run import AppRunner +from metomi.rose.env import env_export +from metomi.rose.opt_parse import RoseOptionParser +from metomi.rose.popen import RosePopenError +from metomi.rose.reporter import Reporter +from metomi.rose.run import Runner +from metomi.rose.task_env import get_prepend_paths import sys import traceback diff --git a/lib/python/rose/tests/test_config.py b/metomi/rose/tests/test_config.py similarity index 89% rename from lib/python/rose/tests/test_config.py rename to metomi/rose/tests/test_config.py index bb76fd58ec..7c19eea8a9 100644 --- a/lib/python/rose/tests/test_config.py +++ b/metomi/rose/tests/test_config.py @@ -18,17 +18,17 @@ # along with Rose. If not, see . # ----------------------------------------------------------------------------- import os.path -import rose.config +import metomi.rose.config from io import StringIO import unittest class TestConfigData(unittest.TestCase): - """Test usage of the rose.config.ConfigNode object.""" + """Test usage of the metomi.rose.config.ConfigNode object.""" def test_init(self): """Test empty Config object.""" - conf = rose.config.ConfigNode() + conf = metomi.rose.config.ConfigNode() self.assertFalse(conf is None) self.assertEqual(conf.get([]), conf) self.assertFalse(conf.get(["rubbish"])) @@ -40,7 +40,7 @@ def test_init(self): def test_set(self): """Test setting/unsetting value/ignored flag in a Config object.""" - conf = rose.config.ConfigNode() + conf = metomi.rose.config.ConfigNode() self.assertFalse(conf is None) self.assertEqual(conf.set([], {}), conf) conf.set(["", "top-option"], "rubbish") @@ -80,7 +80,7 @@ def test_set(self): def test_iter(self): """Test the iterator""" - conf = rose.config.ConfigNode() + conf = metomi.rose.config.ConfigNode() conf.set(["", "food"], "glorious") conf.set(["dinner", "starter"], "soup") conf.set(["dinner", "dessert"], "custard") @@ -90,12 +90,12 @@ def test_iter(self): class TestConfigDump(unittest.TestCase): - """Test usage of the rose.config.Dump object.""" + """Test usage of the metomi.rose.config.Dump object.""" def test_dump_empty(self): """Test dumping an empty configuration.""" - conf = rose.config.ConfigNode({}) - dumper = rose.config.ConfigDumper() + conf = metomi.rose.config.ConfigNode({}) + dumper = metomi.rose.config.ConfigDumper() target = StringIO() dumper.dump(conf, target) self.assertEqual(target.getvalue(), "") @@ -103,7 +103,7 @@ def test_dump_empty(self): def test_dump_normal(self): """Test normal dumping a configuration.""" - conf = rose.config.ConfigNode({}) + conf = metomi.rose.config.ConfigNode({}) conf.set(["foo"], {}) conf.set(["foo", "bar"], "BAR BAR") conf.set(["foo", "baz"], "BAZ\n BAZ") @@ -112,7 +112,7 @@ def test_dump_normal(self): conf.set(["egg", "boiled"], "false") conf.set(["egg", "scrambled"], "false", "!") conf.set(["egg", "poached"], "true", "!!") - dumper = rose.config.ConfigDumper() + dumper = metomi.rose.config.ConfigDumper() target = StringIO() dumper.dump(conf, target) self.assertEqual(target.getvalue(), """[egg] @@ -130,13 +130,13 @@ def test_dump_normal(self): def test_dump_root(self): """Test dumping of a configuration with root settings.""" - conf = rose.config.ConfigNode({}, comments=["hello"]) + conf = metomi.rose.config.ConfigNode({}, comments=["hello"]) conf.set(["foo"], "foo", comments=["foo foo", "foo foo"]) conf.set(["bar"], "bar") conf.set(["baz"], {}) conf.set(["baz", "egg"], "egg") conf.set(["baz", "ham"], "ham") - dumper = rose.config.ConfigDumper() + dumper = metomi.rose.config.ConfigDumper() target = StringIO() dumper.dump(conf, target) self.assertEqual(target.getvalue(), """#hello @@ -154,18 +154,18 @@ def test_dump_root(self): class TestConfigLoad(unittest.TestCase): - """Test usage of the rose.config.Load object.""" + """Test usage of the metomi.rose.config.Load object.""" def test_load_empty(self): """Test loading an empty configuration.""" - conf = rose.config.ConfigNode({}) - loader = rose.config.ConfigLoader() + conf = metomi.rose.config.ConfigNode({}) + loader = metomi.rose.config.ConfigLoader() loader.load(os.path.devnull, conf) self.assertEqual((conf.value, conf.state), ({}, "")) def test_load_basic(self): """Test basic loading a configuration.""" - conf = rose.config.ConfigNode({}) + conf = metomi.rose.config.ConfigNode({}) source = StringIO("""# test stuff=stuffing @@ -191,7 +191,7 @@ def test_load_basic(self): [foo] bar=BAR BAR BAR """) - loader = rose.config.ConfigLoader() + loader = metomi.rose.config.ConfigLoader() loader.load(source, conf) source.close() self.assertEqual(conf.comments, [" test"]) diff --git a/lib/python/rose/tests/test_env.py b/metomi/rose/tests/test_env.py similarity index 98% rename from lib/python/rose/tests/test_env.py rename to metomi/rose/tests/test_env.py index 85ccc96a45..97e0a5769c 100644 --- a/lib/python/rose/tests/test_env.py +++ b/metomi/rose/tests/test_env.py @@ -20,7 +20,7 @@ import os import unittest -from rose.env import env_export +from metomi.rose.env import env_export class _TestEnvExport(unittest.TestCase): diff --git a/lib/python/rose/tests/test_popen.py b/metomi/rose/tests/test_popen.py similarity index 97% rename from lib/python/rose/tests/test_popen.py rename to metomi/rose/tests/test_popen.py index 2ee1429862..59c86f57e1 100644 --- a/lib/python/rose/tests/test_popen.py +++ b/metomi/rose/tests/test_popen.py @@ -21,7 +21,7 @@ import os import unittest -from rose.popen import RosePopenError, RosePopener +from metomi.rose.popen import RosePopenError, RosePopener class _TestOSErrorFilename(unittest.TestCase): diff --git a/lib/python/rose/tests/test_unicode_utils.py b/metomi/rose/tests/test_unicode_utils.py similarity index 97% rename from lib/python/rose/tests/test_unicode_utils.py rename to metomi/rose/tests/test_unicode_utils.py index ca76619491..2fb93d98c6 100644 --- a/lib/python/rose/tests/test_unicode_utils.py +++ b/metomi/rose/tests/test_unicode_utils.py @@ -20,7 +20,7 @@ import io -from rose.unicode_utils import write_safely +from metomi.rose.unicode_utils import write_safely TESTSTR = "Hello World" TESTBYTES = b"Bonjour, Le Monde!" diff --git a/lib/python/rose/unicode_utils.py b/metomi/rose/unicode_utils.py similarity index 100% rename from lib/python/rose/unicode_utils.py rename to metomi/rose/unicode_utils.py diff --git a/lib/python/rose/upgrade.py b/metomi/rose/upgrade.py similarity index 86% rename from lib/python/rose/upgrade.py rename to metomi/rose/upgrade.py index b55e033716..af90a27a0f 100644 --- a/lib/python/rose/upgrade.py +++ b/metomi/rose/upgrade.py @@ -24,10 +24,10 @@ import sys from functools import cmp_to_key -import rose.config -import rose.macro -import rose.macros.trigger -import rose.reporter +import metomi.rose.config +import metomi.rose.macro +import metomi.rose.macros.trigger +import metomi.rose.reporter BEST_VERSION_MARKER = "* " @@ -49,9 +49,9 @@ DOWNGRADE_METHOD = "downgrade" UPGRADE_METHOD = "upgrade" -IGNORE_MAP = {rose.config.ConfigNode.STATE_NORMAL: "enabled", - rose.config.ConfigNode.STATE_USER_IGNORED: "user-ignored", - rose.config.ConfigNode.STATE_SYST_IGNORED: "trig-ignored"} +IGNORE_MAP = {metomi.rose.config.ConfigNode.STATE_NORMAL: "enabled", + metomi.rose.config.ConfigNode.STATE_USER_IGNORED: "user-ignored", + metomi.rose.config.ConfigNode.STATE_SYST_IGNORED: "trig-ignored"} class UpgradeVersionError(NameError): @@ -70,7 +70,7 @@ def __str__(self): return SAME_UPGRADE_VERSION.format(self.args[0]) -class MacroUpgrade(rose.macro.MacroBase): +class MacroUpgrade(metomi.rose.macro.MacroBase): """Class derived from MacroBase to aid upgrade functionality.""" @@ -104,7 +104,8 @@ def act_from_files(self, config, downgrade=False): methods as part of the same upgrade. Args: - config (rose.config.ConfigNode): The application configuration. + config (metomi.rose.config.ConfigNode): The application + configuration. downgrade (bool): True if downgrading. Returns: @@ -114,9 +115,9 @@ def act_from_files(self, config, downgrade=False): add_config = res_map.get(MACRO_UPGRADE_RESOURCE_FILE_ADD) rem_config = res_map.get(MACRO_UPGRADE_RESOURCE_FILE_REMOVE) if add_config is None: - add_config = rose.config.ConfigNode() + add_config = metomi.rose.config.ConfigNode() if rem_config is None: - rem_config = rose.config.ConfigNode() + rem_config = metomi.rose.config.ConfigNode() if downgrade: add_config, rem_config = rem_config, add_config for keys, node in add_config.walk(): @@ -150,7 +151,7 @@ def _get_config_resources(self): file_map[MACRO_UPGRADE_RESOURCE_FILE_REMOVE] = rem_path for key, path in file_map.items(): if os.path.isfile(path): - file_map[key] = rose.config.load(path) + file_map[key] = metomi.rose.config.load(path) else: file_map.pop(key) return file_map @@ -160,7 +161,8 @@ def add_setting(self, config, keys, value=None, forced=False, """Add a setting to the configuration. Args: - config (rose.config.ConfigNode): The application configuration. + config (metomi.rose.config.ConfigNode): The application + configuration. keys (list): A list defining a hierarchy of node.value 'keys'. A section will be a list of one keys, an option will have two. value (string - optional): String denoting the new setting value. @@ -199,7 +201,7 @@ def add_setting(self, config, keys, value=None, forced=False, if not existing_section.startswith(section): continue existing_base_section = ( - rose.macro.REC_ID_STRIP.sub("", existing_section)) + metomi.rose.macro.REC_ID_STRIP.sub("", existing_section)) if option is None: # For section 'foo', look for 'foo', 'foo{bar}', 'foo(1)'. found_setting = (existing_section == section or @@ -215,7 +217,7 @@ def add_setting(self, config, keys, value=None, forced=False, for keys, _ in config.walk([existing_section]): existing_option = keys[1] existing_base_option = ( - rose.macro.REC_ID_STRIP_DUPL.sub( + metomi.rose.macro.REC_ID_STRIP_DUPL.sub( "", existing_option) ) # For option 'foo', look for 'foo', 'foo(1)'. @@ -263,7 +265,8 @@ def change_setting_value(self, config, keys, value, forced=False, """Change a setting (option) value in the configuration. Args: - config (rose.config.ConfigNode): The application configuration. + config (metomi.rose.config.ConfigNode): The application + configuration. keys (list): A list defining a hierarchy of node.value 'keys'. A section will be a list of one keys, an option will have two. value (string): The new value. Required for options, can be @@ -305,7 +308,8 @@ def get_setting_value(self, config, keys, no_ignore=False): """Return the value of a setting or ``None`` if not set. Args: - config (rose.config.ConfigNode): The application configuration. + config (metomi.rose.config.ConfigNode): The application + configuration. keys (list): A list defining a hierarchy of node.value 'keys'. A section will be a list of one keys, an option will have two. no_ignore (bool - optional): If ``True`` return ``None`` if the @@ -323,7 +327,8 @@ def remove_setting(self, config, keys, info=None): """Remove a setting from the configuration. Args: - config (rose.config.ConfigNode): The application configuration. + config (metomi.rose.config.ConfigNode): The application + configuration. keys (list): A list defining a hierarchy of node.value 'keys'. A section will be a list of one keys, an option will have two. info (string - optional): A short string containing no new lines, @@ -346,7 +351,8 @@ def rename_setting(self, config, keys, new_keys, info=None): """Rename a setting in the configuration. Args: - config (rose.config.ConfigNode): The application configuration. + config (metomi.rose.config.ConfigNode): The application + configuration. keys (list): A list defining a hierarchy of node.value 'keys'. A section will be a list of one keys, an option will have two. new_keys (list): The new hierarchy of node.value 'keys'. @@ -396,7 +402,8 @@ def enable_setting(self, config, keys, info=None): """Enable a setting in the configuration. Args: - config (rose.config.ConfigNode): The application configuration. + config (metomi.rose.config.ConfigNode): The application + configuration. keys (list): A list defining a hierarchy of node.value 'keys'. A section will be a list of one keys, an option will have two. info (string - optional): A short string containing no new lines, @@ -405,16 +412,19 @@ def enable_setting(self, config, keys, info=None): Returns: False - if the setting's state is not changed else ``None``. """ - return self._ignore_setting(config, list(keys), - info=info, - state=rose.config.ConfigNode.STATE_NORMAL) + return self._ignore_setting( + config, + list(keys), + info=info, + state=metomi.rose.config.ConfigNode.STATE_NORMAL) def ignore_setting(self, config, keys, info=None, - state=rose.config.ConfigNode.STATE_USER_IGNORED): + state=metomi.rose.config.ConfigNode.STATE_USER_IGNORED): """User-ignore a setting in the configuration. Args: - config (rose.config.ConfigNode): The application configuration. + config (metomi.rose.config.ConfigNode): The application + configuration. keys (list): A list defining a hierarchy of node.value 'keys'. A section will be a list of one keys, an option will have two. info (string - optional): A short string containing no new lines, @@ -473,8 +483,9 @@ def __init__(self, app_config, downgrade=False): self.downgrade = downgrade self.new_version = None self.named_tags = [] - opt_node = app_config.get([rose.CONFIG_SECT_TOP, - rose.CONFIG_OPT_META_TYPE], no_ignore=True) + opt_node = app_config.get( + [metomi.rose.CONFIG_SECT_TOP, metomi.rose.CONFIG_OPT_META_TYPE], + no_ignore=True) tag_items = opt_node.value.split("/") if len(tag_items) > 1: self.tag = tag_items.pop(-1) @@ -489,14 +500,15 @@ def __init__(self, app_config, downgrade=False): def load_all_tags(self): """Load an ordered list of the available upgrade macros.""" - meta_path = rose.macro.load_meta_path( + meta_path = metomi.rose.macro.load_meta_path( self.app_config, is_upgrade=True)[0] if meta_path is None: - raise OSError(rose.macro.ERROR_LOAD_CONF_META_NODE) + raise OSError(metomi.rose.macro.ERROR_LOAD_CONF_META_NODE) meta_path = os.path.abspath(meta_path) self.named_tags = [] for node in os.listdir(meta_path): - node_meta = os.path.join(meta_path, node, rose.META_CONFIG_NAME) + node_meta = os.path.join( + meta_path, node, metomi.rose.META_CONFIG_NAME) if os.path.exists(node_meta): self.named_tags.append(node) self.version_module = get_meta_upgrade_module(meta_path) @@ -504,7 +516,7 @@ def load_all_tags(self): # No versions.py. self._load_version_macros([]) return - macro_info_tuples = rose.macro.get_macro_class_methods( + macro_info_tuples = metomi.rose.macro.get_macro_class_methods( [self.version_module]) version_macros = [] if self.downgrade: @@ -587,21 +599,23 @@ def transform(self, config, meta_config=None, opt_non_interactive=False, if custom_inspector: res = custom_inspector(optionals, "upgrade_macro") else: - res = rose.macro.get_user_values(optionals) + res = metomi.rose.macro.get_user_values(optionals) upgrade_macro_result = func(config, meta_config, **res) config, i_changes = upgrade_macro_result self.reports += i_changes - opt_node = config.get([rose.CONFIG_SECT_TOP, - rose.CONFIG_OPT_META_TYPE], no_ignore=True) + opt_node = config.get( + [metomi.rose.CONFIG_SECT_TOP, metomi.rose.CONFIG_OPT_META_TYPE], + no_ignore=True) new_value = self.meta_flag_no_tag + "/" + self.new_tag opt_node.value = new_value if self.downgrade: info = INFO_DOWNGRADED.format(self.tag, self.new_tag) else: info = INFO_UPGRADED.format(self.tag, self.new_tag) - report = rose.macro.MacroReport(rose.CONFIG_SECT_TOP, - rose.CONFIG_OPT_META_TYPE, - new_value, info) + report = metomi.rose.macro.MacroReport( + metomi.rose.CONFIG_SECT_TOP, + metomi.rose.CONFIG_OPT_META_TYPE, + new_value, info) self.reports += [report] return config, self.reports @@ -682,7 +696,7 @@ def get_meta_upgrade_module(meta_path): def parse_upgrade_args(argv=None): """Parse options/arguments for rose macro and upgrade.""" - opt_parser = rose.macro.RoseOptionParser() + opt_parser = metomi.rose.macro.RoseOptionParser() options = ["conf_dir", "meta_path", "non_interactive", "output_dir", "downgrade", "all_versions"] opt_parser.add_my_options(*options) @@ -698,21 +712,22 @@ def parse_upgrade_args(argv=None): opts.conf_dir = os.path.abspath(opts.conf_dir) if opts.output_dir is not None: opts.output_dir = os.path.abspath(opts.output_dir) - sys.path.append(os.getenv("ROSE_HOME")) - rose.macro.add_opt_meta_paths(opts.meta_path) + sys.path.append(os.getenv("ROSE_LIB")) + metomi.rose.macro.add_opt_meta_paths(opts.meta_path) config_name = os.path.basename(opts.conf_dir) config_file_path = os.path.join(opts.conf_dir, - rose.SUB_CONFIG_NAME) + metomi.rose.SUB_CONFIG_NAME) if (not os.path.exists(config_file_path) or not os.path.isfile(config_file_path)): - rose.reporter.Reporter()(rose.macro.ERROR_LOAD_CONFIG_DIR.format( - opts.conf_dir), - kind=rose.reporter.Reporter.KIND_ERR, - level=rose.reporter.Reporter.FAIL) + metomi.rose.reporter.Reporter()( + metomi.rose.macro.ERROR_LOAD_CONFIG_DIR.format( + opts.conf_dir), + kind=metomi.rose.reporter.Reporter.KIND_ERR, + level=metomi.rose.reporter.Reporter.FAIL) return None return ( - rose.macro.load_conf_from_file( + metomi.rose.macro.load_conf_from_file( opts.conf_dir, config_file_path, mode="upgrade") + (config_name, args, opts,) ) @@ -728,12 +743,12 @@ def main(): if opts.conf_dir is not None: os.chdir(opts.conf_dir) verbosity = 1 + opts.verbosity - opts.quietness - reporter = rose.reporter.Reporter(verbosity) - meta_opt_node = app_config.get([rose.CONFIG_SECT_TOP, - rose.CONFIG_OPT_META_TYPE], + reporter = metomi.rose.reporter.Reporter(verbosity) + meta_opt_node = app_config.get([metomi.rose.CONFIG_SECT_TOP, + metomi.rose.CONFIG_OPT_META_TYPE], no_ignore=True) if meta_opt_node is None or len(meta_opt_node.value.split("/")) < 2: - reporter(rose.macro.MetaConfigFlagMissingError()) + reporter(metomi.rose.macro.MetaConfigFlagMissingError()) sys.exit(1) try: upgrade_manager = MacroUpgradeManager(app_config, opts.downgrade) @@ -766,7 +781,7 @@ def main(): reporter(UpgradeVersionError(user_choice)) sys.exit(1) upgrade_manager.set_new_tag(user_choice) - combined_config_map = rose.macro.combine_opt_config_map(config_map) + combined_config_map = metomi.rose.macro.combine_opt_config_map(config_map) macro_function = ( lambda conf, meta, conf_key: upgrade_manager.transform( conf, meta, opts.non_interactive) @@ -774,42 +789,44 @@ def main(): method_id = UPGRADE_METHOD.upper()[0] if opts.downgrade: method_id = DOWNGRADE_METHOD.upper()[0] - macro_id = rose.macro.MACRO_OUTPUT_ID.format(method_id, - upgrade_manager.get_name()) - new_config_map, changes_map = rose.macro.apply_macro_to_config_map( + macro_id = metomi.rose.macro.MACRO_OUTPUT_ID.format( + method_id, upgrade_manager.get_name()) + new_config_map, changes_map = metomi.rose.macro.apply_macro_to_config_map( combined_config_map, meta_config, macro_function, macro_name=macro_id) sys.stdout.flush() # Ensure text from macro output before next fn - has_changes = rose.macro.handle_transform(config_map, new_config_map, - changes_map, macro_id, - opts.conf_dir, opts.output_dir, - opts.non_interactive, reporter) + has_changes = metomi.rose.macro.handle_transform( + config_map, new_config_map, + changes_map, macro_id, + opts.conf_dir, opts.output_dir, + opts.non_interactive, reporter) if not has_changes: return - new_meta_config = rose.macro.load_meta_config( + new_meta_config = metomi.rose.macro.load_meta_config( new_config_map[None], directory=opts.conf_dir, - config_type=rose.SUB_CONFIG_NAME, + config_type=metomi.rose.SUB_CONFIG_NAME, ignore_meta_error=True ) config_map = new_config_map - combined_config_map = rose.macro.combine_opt_config_map(config_map) + combined_config_map = metomi.rose.macro.combine_opt_config_map(config_map) macro_function = ( lambda conf, meta, conf_key: - rose.macros.trigger.TriggerMacro().transform(conf, meta) + metomi.rose.macros.trigger.TriggerMacro().transform(conf, meta) ) - new_config_map, changes_map = rose.macro.apply_macro_to_config_map( + new_config_map, changes_map = metomi.rose.macro.apply_macro_to_config_map( combined_config_map, new_meta_config, macro_function, macro_name=macro_id) - trig_macro_id = rose.macro.MACRO_OUTPUT_ID.format( - rose.macro.TRANSFORM_METHOD.upper()[0], + trig_macro_id = metomi.rose.macro.MACRO_OUTPUT_ID.format( + metomi.rose.macro.TRANSFORM_METHOD.upper()[0], MACRO_UPGRADE_TRIGGER_NAME ) if any(changes_map.values()): - rose.macro.handle_transform(config_map, new_config_map, - changes_map, trig_macro_id, - opts.conf_dir, opts.output_dir, - opts.non_interactive, reporter) + metomi.rose.macro.handle_transform( + config_map, new_config_map, + changes_map, trig_macro_id, + opts.conf_dir, opts.output_dir, + opts.non_interactive, reporter) if __name__ == "__main__": - rose.macro.add_meta_paths() + metomi.rose.macro.add_meta_paths() main() diff --git a/lib/python/rose/variable.py b/metomi/rose/variable.py similarity index 95% rename from lib/python/rose/variable.py rename to metomi/rose/variable.py index 85cbadca99..3c3ad8d73d 100644 --- a/lib/python/rose/variable.py +++ b/metomi/rose/variable.py @@ -29,7 +29,7 @@ import copy import re -import rose +import metomi.rose RE_REAL = r"[\+\-]?\d*\.?\d*(?:[de][\+\-]?\d+)?" @@ -43,7 +43,7 @@ r"(?" + markup + " " return markup @@ -256,22 +256,23 @@ def _is_quote_state_change(string, index, quote_lookup, quote_state): def get_value_from_metadata(meta_data): """Use raw metadata to get a 'correct' value for a variable.""" var_value = '' - if rose.META_PROP_VALUES in meta_data: - var_value = array_split(meta_data[rose.META_PROP_VALUES])[0] - elif rose.META_PROP_TYPE in meta_data: - var_type = meta_data[rose.META_PROP_TYPE] + if metomi.rose.META_PROP_VALUES in meta_data: + var_value = array_split(meta_data[metomi.rose.META_PROP_VALUES])[0] + elif metomi.rose.META_PROP_TYPE in meta_data: + var_type = meta_data[metomi.rose.META_PROP_TYPE] if var_type == 'logical': - var_value = rose.TYPE_LOGICAL_VALUE_FALSE + var_value = metomi.rose.TYPE_LOGICAL_VALUE_FALSE elif var_type == 'boolean': - var_value = rose.TYPE_BOOLEAN_VALUE_FALSE + var_value = metomi.rose.TYPE_BOOLEAN_VALUE_FALSE elif var_type in ['integer', 'real']: var_value = '0' elif var_type == 'character': var_value = "''" elif var_type == 'quoted': var_value = '""' - elif rose.META_PROP_VALUE_HINTS in meta_data: - var_value = array_split(meta_data[rose.META_PROP_VALUE_HINTS])[0] + elif metomi.rose.META_PROP_VALUE_HINTS in meta_data: + var_value = array_split( + meta_data[metomi.rose.META_PROP_VALUE_HINTS])[0] return var_value diff --git a/lib/python/rosie/__init__.py b/metomi/rosie/__init__.py similarity index 100% rename from lib/python/rosie/__init__.py rename to metomi/rosie/__init__.py diff --git a/lib/python/rosie/db.py b/metomi/rosie/db.py similarity index 100% rename from lib/python/rosie/db.py rename to metomi/rosie/db.py diff --git a/lib/python/rosie/db_create.py b/metomi/rosie/db_create.py similarity index 96% rename from lib/python/rosie/db_create.py rename to metomi/rosie/db_create.py index d3e9e06927..d7b453aff1 100644 --- a/lib/python/rosie/db_create.py +++ b/metomi/rosie/db_create.py @@ -23,14 +23,14 @@ import sqlalchemy as al import sys -from rose.fs_util import FileSystemUtil -from rose.opt_parse import RoseOptionParser -from rose.popen import RosePopener -from rose.reporter import Reporter, Event -from rose.resource import ResourceLocator -from rosie.db import ( +from metomi.rose.fs_util import FileSystemUtil +from metomi.rose.opt_parse import RoseOptionParser +from metomi.rose.popen import RosePopener +from metomi.rose.reporter import Reporter, Event +from metomi.rose.resource import ResourceLocator +from metomi.rosie.db import ( LATEST_TABLE_NAME, MAIN_TABLE_NAME, META_TABLE_NAME, OPTIONAL_TABLE_NAME) -from rosie.svn_post_commit import RosieSvnPostCommitHook +from metomi.rosie.svn_post_commit import RosieSvnPostCommitHook class RosieDatabaseCreateEvent(Event): diff --git a/lib/python/rosie/graph.py b/metomi/rosie/graph.py similarity index 88% rename from lib/python/rosie/graph.py rename to metomi/rosie/graph.py index f11abc0caa..b283332d8c 100644 --- a/lib/python/rosie/graph.py +++ b/metomi/rosie/graph.py @@ -22,31 +22,33 @@ import textwrap import time -import rose.metadata_graph -import rose.opt_parse -import rose.reporter -import rosie.suite_id -import rosie.ws_client -import rosie.ws_client_cli +import pygraphviz +import metomi.rose.metadata_graph +import metomi.rose.opt_parse +import metomi.rose.reporter +import metomi.rosie.suite_id +import metomi.rosie.ws_client +import metomi.rosie.ws_client_cli -class NoConnectionsEvent(rose.reporter.Event): + +class NoConnectionsEvent(metomi.rose.reporter.Event): """An event raised if the graph has no edges or nodes. event.args[0] is the filter id string. """ - KIND = rose.reporter.Reporter.KIND_ERR + KIND = metomi.rose.reporter.Reporter.KIND_ERR def __str__(self): return "%s: no copy relationships to other suites" % self.args[0] -class PrintSuiteDetails(rose.reporter.Event): +class PrintSuiteDetails(metomi.rose.reporter.Event): """An event to print out suite details when writing to CLI""" - KIND = rose.reporter.Reporter.KIND_OUT + KIND = metomi.rose.reporter.Reporter.KIND_OUT def __str__(self): template = " %s" @@ -68,13 +70,13 @@ def get_suite_data(prefix, properties=None): if properties is None: properties = [] - ws_client = rosie.ws_client.RosieWSClient( + ws_client = metomi.rosie.ws_client.RosieWSClient( prefixes=[prefix], - event_handler=rose.reporter.Reporter() + event_handler=metomi.rose.reporter.Reporter() ) suite_data = ws_client.search(prefix, all_revs=1)[0][0] for dict_row in sorted(suite_data, key=lambda _: _["revision"]): - suite_id = rosie.suite_id.SuiteId.from_idx_branch_revision( + suite_id = metomi.rosie.suite_id.SuiteId.from_idx_branch_revision( dict_row["idx"], dict_row["branch"], dict_row["revision"] @@ -84,7 +86,7 @@ def get_suite_data(prefix, properties=None): dict_row["local"] = suite_id.get_status() if "date" in properties: dict_row["date"] = time.strftime( - rosie.ws_client_cli.DATE_TIME_FORMAT, + metomi.rosie.ws_client_cli.DATE_TIME_FORMAT, time.gmtime(dict_row.get("date")) ) @@ -126,7 +128,7 @@ def calculate_edges(graph, suite_data, filter_id=None, properties=None, add_node(graph, node1, node_rosie_properties.get(node1)) graph.add_edge(edge[0], edge[1]) else: - reporter = rose.reporter.Reporter() + reporter = metomi.rose.reporter.Reporter() # Only plot the connections involving filter_id. node_stack = [] @@ -170,7 +172,6 @@ def add_node(graph, node, node_label_properties, **kwargs): def make_graph(suite_data, filter_id, properties, prefix, max_distance=None): """Construct the pygraphviz graph.""" - import pygraphviz graph = pygraphviz.AGraph(directed=True) graph.graph_attr["rankdir"] = "LR" if filter_id: @@ -184,8 +185,8 @@ def make_graph(suite_data, filter_id, properties, prefix, max_distance=None): def output_graph(graph, filename=None, debug_mode=False): """Draw the graph to filename (or temporary file if None).""" - rose.metadata_graph.output_graph(graph, debug_mode=debug_mode, - filename=filename) + metomi.rose.metadata_graph.output_graph(graph, debug_mode=debug_mode, + filename=filename) def print_graph(suite_data, filter_id, properties=None, max_distance=None): @@ -193,7 +194,7 @@ def print_graph(suite_data, filter_id, properties=None, max_distance=None): if properties is None: properties = [] - reporter = rose.reporter.Reporter() + reporter = metomi.rose.reporter.Reporter() ancestry = {} # Process suite_data to get ancestry tree @@ -245,7 +246,7 @@ def print_graph(suite_data, filter_id, properties=None, max_distance=None): def main(): """Provide the CLI interface.""" - opt_parser = rose.opt_parse.RoseOptionParser() + opt_parser = metomi.rose.opt_parse.RoseOptionParser() opt_parser.add_my_options("distance", "output_file", "prefix", @@ -255,13 +256,13 @@ def main(): filter_id = None if args: filter_id = args[0] - prefix = rosie.suite_id.SuiteId(id_text=filter_id).prefix + prefix = metomi.rosie.suite_id.SuiteId(id_text=filter_id).prefix if opts.prefix: opt_parser.error("No need to specify --prefix when specifying ID") elif opts.prefix: prefix = opts.prefix else: - prefix = rosie.suite_id.SuiteId.get_prefix_default() + prefix = metomi.rosie.suite_id.SuiteId.get_prefix_default() if opts.distance and not args: opt_parser.error("distance option requires an ID") if opts.text and not args: diff --git a/lib/html/static/css/bootstrap.min.css b/metomi/rosie/lib/html/static/css/bootstrap.min.css similarity index 100% rename from lib/html/static/css/bootstrap.min.css rename to metomi/rosie/lib/html/static/css/bootstrap.min.css diff --git a/lib/html/static/css/dataTables.bootstrap.css b/metomi/rosie/lib/html/static/css/dataTables.bootstrap.css similarity index 100% rename from lib/html/static/css/dataTables.bootstrap.css rename to metomi/rosie/lib/html/static/css/dataTables.bootstrap.css diff --git a/lib/html/static/css/jquery.dataTables.css b/metomi/rosie/lib/html/static/css/jquery.dataTables.css similarity index 100% rename from lib/html/static/css/jquery.dataTables.css rename to metomi/rosie/lib/html/static/css/jquery.dataTables.css diff --git a/lib/html/static/fonts/glyphicons-halflings-regular.eot b/metomi/rosie/lib/html/static/fonts/glyphicons-halflings-regular.eot similarity index 100% rename from lib/html/static/fonts/glyphicons-halflings-regular.eot rename to metomi/rosie/lib/html/static/fonts/glyphicons-halflings-regular.eot diff --git a/lib/html/static/fonts/glyphicons-halflings-regular.svg b/metomi/rosie/lib/html/static/fonts/glyphicons-halflings-regular.svg similarity index 100% rename from lib/html/static/fonts/glyphicons-halflings-regular.svg rename to metomi/rosie/lib/html/static/fonts/glyphicons-halflings-regular.svg diff --git a/lib/html/static/fonts/glyphicons-halflings-regular.ttf b/metomi/rosie/lib/html/static/fonts/glyphicons-halflings-regular.ttf similarity index 100% rename from lib/html/static/fonts/glyphicons-halflings-regular.ttf rename to metomi/rosie/lib/html/static/fonts/glyphicons-halflings-regular.ttf diff --git a/lib/html/static/fonts/glyphicons-halflings-regular.woff b/metomi/rosie/lib/html/static/fonts/glyphicons-halflings-regular.woff similarity index 100% rename from lib/html/static/fonts/glyphicons-halflings-regular.woff rename to metomi/rosie/lib/html/static/fonts/glyphicons-halflings-regular.woff diff --git a/lib/html/static/fonts/glyphicons-halflings-regular.woff2 b/metomi/rosie/lib/html/static/fonts/glyphicons-halflings-regular.woff2 similarity index 100% rename from lib/html/static/fonts/glyphicons-halflings-regular.woff2 rename to metomi/rosie/lib/html/static/fonts/glyphicons-halflings-regular.woff2 diff --git a/lib/html/static/images/sort_asc.png b/metomi/rosie/lib/html/static/images/sort_asc.png similarity index 100% rename from lib/html/static/images/sort_asc.png rename to metomi/rosie/lib/html/static/images/sort_asc.png diff --git a/lib/html/static/images/sort_asc_disabled.png b/metomi/rosie/lib/html/static/images/sort_asc_disabled.png similarity index 100% rename from lib/html/static/images/sort_asc_disabled.png rename to metomi/rosie/lib/html/static/images/sort_asc_disabled.png diff --git a/lib/html/static/images/sort_both.png b/metomi/rosie/lib/html/static/images/sort_both.png similarity index 100% rename from lib/html/static/images/sort_both.png rename to metomi/rosie/lib/html/static/images/sort_both.png diff --git a/lib/html/static/images/sort_desc.png b/metomi/rosie/lib/html/static/images/sort_desc.png similarity index 100% rename from lib/html/static/images/sort_desc.png rename to metomi/rosie/lib/html/static/images/sort_desc.png diff --git a/lib/html/static/images/sort_desc_disabled.png b/metomi/rosie/lib/html/static/images/sort_desc_disabled.png similarity index 100% rename from lib/html/static/images/sort_desc_disabled.png rename to metomi/rosie/lib/html/static/images/sort_desc_disabled.png diff --git a/lib/html/static/img b/metomi/rosie/lib/html/static/img similarity index 100% rename from lib/html/static/img rename to metomi/rosie/lib/html/static/img diff --git a/lib/html/static/js/bootstrap.min.js b/metomi/rosie/lib/html/static/js/bootstrap.min.js similarity index 100% rename from lib/html/static/js/bootstrap.min.js rename to metomi/rosie/lib/html/static/js/bootstrap.min.js diff --git a/lib/html/static/js/dataTables.bootstrap.js b/metomi/rosie/lib/html/static/js/dataTables.bootstrap.js similarity index 100% rename from lib/html/static/js/dataTables.bootstrap.js rename to metomi/rosie/lib/html/static/js/dataTables.bootstrap.js diff --git a/lib/html/static/js/jquery.dataTables.js b/metomi/rosie/lib/html/static/js/jquery.dataTables.js similarity index 100% rename from lib/html/static/js/jquery.dataTables.js rename to metomi/rosie/lib/html/static/js/jquery.dataTables.js diff --git a/lib/html/static/js/jquery.dataTables.min.js b/metomi/rosie/lib/html/static/js/jquery.dataTables.min.js similarity index 100% rename from lib/html/static/js/jquery.dataTables.min.js rename to metomi/rosie/lib/html/static/js/jquery.dataTables.min.js diff --git a/lib/html/static/js/jquery.min.js b/metomi/rosie/lib/html/static/js/jquery.min.js similarity index 100% rename from lib/html/static/js/jquery.min.js rename to metomi/rosie/lib/html/static/js/jquery.min.js diff --git a/lib/html/static/js/livestamp.min.js b/metomi/rosie/lib/html/static/js/livestamp.min.js similarity index 100% rename from lib/html/static/js/livestamp.min.js rename to metomi/rosie/lib/html/static/js/livestamp.min.js diff --git a/lib/html/static/js/moment.min.js b/metomi/rosie/lib/html/static/js/moment.min.js similarity index 100% rename from lib/html/static/js/moment.min.js rename to metomi/rosie/lib/html/static/js/moment.min.js diff --git a/lib/html/static/js/rosie-disco.js b/metomi/rosie/lib/html/static/js/rosie-disco.js similarity index 100% rename from lib/html/static/js/rosie-disco.js rename to metomi/rosie/lib/html/static/js/rosie-disco.js diff --git a/lib/html/static/rosie-favicon.png b/metomi/rosie/lib/html/static/rosie-favicon.png similarity index 100% rename from lib/html/static/rosie-favicon.png rename to metomi/rosie/lib/html/static/rosie-favicon.png diff --git a/lib/html/template/rosie-disco/index.html b/metomi/rosie/lib/html/template/rosie-disco/index.html similarity index 100% rename from lib/html/template/rosie-disco/index.html rename to metomi/rosie/lib/html/template/rosie-disco/index.html diff --git a/lib/html/template/rosie-disco/prefix-index.html b/metomi/rosie/lib/html/template/rosie-disco/prefix-index.html similarity index 100% rename from lib/html/template/rosie-disco/prefix-index.html rename to metomi/rosie/lib/html/template/rosie-disco/prefix-index.html diff --git a/lib/python/rosie/suite_id.py b/metomi/rosie/suite_id.py similarity index 97% rename from lib/python/rosie/suite_id.py rename to metomi/rosie/suite_id.py index c58b615e4b..4869bfde71 100644 --- a/lib/python/rosie/suite_id.py +++ b/metomi/rosie/suite_id.py @@ -30,13 +30,13 @@ import os import re -import rose.env -from rose.loc_handlers.svn import SvnInfoXMLParser -from rose.opt_parse import RoseOptionParser -from rose.popen import RosePopener, RosePopenError -from rose.reporter import Reporter -from rose.resource import ResourceLocator -from rose.suite_engine_proc import SuiteEngineProcessor, NoSuiteLogError +import metomi.rose.env +from metomi.rose.loc_handlers.svn import SvnInfoXMLParser +from metomi.rose.opt_parse import RoseOptionParser +from metomi.rose.popen import RosePopener, RosePopenError +from metomi.rose.reporter import Reporter +from metomi.rose.resource import ResourceLocator +from metomi.rose.suite_engine_proc import SuiteEngineProcessor, NoSuiteLogError import shlex import string import sys @@ -217,7 +217,7 @@ def get_local_copy_root(cls, user=None): local_copy_root = os.path.expanduser( os.path.join("~" + user, "roses")) elif value: - local_copy_root = rose.env.env_var_process(value) + local_copy_root = metomi.rose.env.env_var_process(value) else: local_copy_root = os.path.expanduser(os.path.join("~", "roses")) return local_copy_root diff --git a/lib/python/rosie/svn_post_commit.py b/metomi/rosie/svn_post_commit.py similarity index 96% rename from lib/python/rosie/svn_post_commit.py rename to metomi/rosie/svn_post_commit.py index 5c4de94329..1d983f2e76 100644 --- a/lib/python/rosie/svn_post_commit.py +++ b/metomi/rosie/svn_post_commit.py @@ -39,13 +39,13 @@ from time import mktime, strptime import traceback -import rose.config -from rose.opt_parse import RoseOptionParser -from rose.popen import RosePopener, RosePopenError -from rose.reporter import Reporter -from rose.resource import ResourceLocator -from rose.scheme_handler import SchemeHandlersManager -from rosie.db import ( +import metomi.rose.config +from metomi.rose.opt_parse import RoseOptionParser +from metomi.rose.popen import RosePopener, RosePopenError +from metomi.rose.reporter import Reporter +from metomi.rose.resource import ResourceLocator +from metomi.rose.scheme_handler import SchemeHandlersManager +from metomi.rosie.db import ( LATEST_TABLE_NAME, MAIN_TABLE_NAME, META_TABLE_NAME, OPTIONAL_TABLE_NAME) @@ -123,7 +123,8 @@ def __init__(self, event_handler=None, popen=None): if popen is None: popen = RosePopener(self.event_handler) self.popen = popen - path = os.path.dirname(os.path.dirname(sys.modules["rosie"].__file__)) + path = os.path.dirname(os.path.dirname( + sys.modules["metomi.rosie"].__file__)) self.usertools_manager = SchemeHandlersManager( [path], "rosie.usertools", ["get_emails"]) @@ -132,8 +133,8 @@ def run(self, repos, revision, no_notification=False): # Lookup prefix of repos # Do nothing if prefix is not registered conf = ResourceLocator.default().get_conf() - rosie_db_node = conf.get(["rosie-db"], no_ignore=True) - for key, node in rosie_db_node.value.items(): + metomi.rosie.db_node = conf.get(["rosie-db"], no_ignore=True) + for key, node in metomi.rosie.db_node.value.items(): if node.is_ignored() or not key.startswith("repos."): continue if os.path.realpath(repos) == os.path.realpath(node.value): @@ -282,7 +283,7 @@ def _load_info(self, repos, revision, sid, branch): except RosePopenError: return None t_handle.seek(0) - config = rose.config.load(t_handle) + config = metomi.rose.config.load(t_handle) t_handle.close() return config @@ -327,9 +328,9 @@ def _notify_trunk_changes(self, changeset_attribs, branch_attribs): if (changed_line[4:].strip() == info_file_path and branch_attribs["status_info_file"] == self.ST_MODIFIED): old_strio = StringIO() - rose.config.dump(branch_attribs["old_info"], old_strio) + metomi.rose.config.dump(branch_attribs["old_info"], old_strio) new_strio = StringIO() - rose.config.dump(branch_attribs["info"], new_strio) + metomi.rose.config.dump(branch_attribs["info"], new_strio) for diff_line in unified_diff( old_strio.getvalue().splitlines(True), new_strio.getvalue().splitlines(True), diff --git a/lib/python/rosie/svn_pre_commit.py b/metomi/rosie/svn_pre_commit.py similarity index 95% rename from lib/python/rosie/svn_pre_commit.py rename to metomi/rosie/svn_pre_commit.py index 6c7acb4008..c0ef8dc121 100755 --- a/lib/python/rosie/svn_pre_commit.py +++ b/metomi/rosie/svn_pre_commit.py @@ -27,15 +27,17 @@ from fnmatch import fnmatch import os import re -import rose -from rose.config import ConfigLoader -from rose.opt_parse import RoseOptionParser -from rose.macro import add_meta_paths, get_reports_as_text, load_meta_config -from rose.macros import DefaultValidators -from rose.popen import RosePopener -from rose.reporter import Reporter -from rose.resource import ResourceLocator -from rose.scheme_handler import SchemeHandlersManager +import metomi.rose +from metomi.rose.config import ConfigLoader +from metomi.rose.opt_parse import RoseOptionParser +from metomi.rose.macro import (add_meta_paths, + get_reports_as_text, + load_meta_config) +from metomi.rose.macros import DefaultValidators +from metomi.rose.popen import RosePopener +from metomi.rose.reporter import Reporter +from metomi.rose.resource import ResourceLocator +from metomi.rose.scheme_handler import SchemeHandlersManager import shlex import sys import tempfile @@ -105,7 +107,9 @@ def __init__(self, event_handler=None, popen=None): if popen is None: popen = RosePopener(self.event_handler) self.popen = popen - path = os.path.dirname(os.path.dirname(sys.modules["rosie"].__file__)) + path = os.path.dirname( + os.path.dirname(sys.modules["metomi.rosie"].__file__) + ) self.usertools_manager = SchemeHandlersManager( [path], "rosie.usertools", ["verify_users"]) @@ -272,7 +276,7 @@ def run(self, repos, txn): txn_info_map[path_head], load_meta_config( txn_info_map[path_head], - config_type=rose.INFO_CONFIG_NAME)) + config_type=metomi.rose.INFO_CONFIG_NAME)) if reports: reports_str = get_reports_as_text({None: reports}, path) bad_changes.append( diff --git a/lib/python/rosie/usertools/__init__.py b/metomi/rosie/usertools/__init__.py similarity index 100% rename from lib/python/rosie/usertools/__init__.py rename to metomi/rosie/usertools/__init__.py diff --git a/lib/python/rosie/usertools/ldaptool.py b/metomi/rosie/usertools/ldaptool.py similarity index 97% rename from lib/python/rosie/usertools/ldaptool.py rename to metomi/rosie/usertools/ldaptool.py index 9afde07c7b..0f172040a6 100644 --- a/lib/python/rosie/usertools/ldaptool.py +++ b/metomi/rosie/usertools/ldaptool.py @@ -20,11 +20,11 @@ """User information via LDAP.""" try: - import ldap + import ldap3 as ldap except ImportError: pass import os -from rose.resource import ResourceLocator +from metomi.rose.resource import ResourceLocator class LDAPUserTool(object): diff --git a/lib/python/rosie/usertools/passwdtool.py b/metomi/rosie/usertools/passwdtool.py similarity index 100% rename from lib/python/rosie/usertools/passwdtool.py rename to metomi/rosie/usertools/passwdtool.py diff --git a/lib/python/rosie/vc.py b/metomi/rosie/vc.py similarity index 93% rename from lib/python/rosie/vc.py rename to metomi/rosie/vc.py index 6277ef8156..b749f84a99 100644 --- a/lib/python/rosie/vc.py +++ b/metomi/rosie/vc.py @@ -23,18 +23,20 @@ from fnmatch import fnmatch import os import pwd -import rose.config -import rose.external -import rose.metadata_check -import rose.reporter -from rose.fs_util import FileSystemUtil -from rose.macro import add_meta_paths, load_meta_config -from rose.macros import DefaultValidators -from rose.opt_parse import RoseOptionParser -from rose.popen import RosePopener, RosePopenError -from rose.reporter import Event, Reporter -from rose.resource import ResourceLocator -from rosie.suite_id import SuiteId, SuiteIdOverflowError, SuiteIdPrefixError +import metomi.rose.config +import metomi.rose.external +import metomi.rose.metadata_check +import metomi.rose.reporter +from metomi.rose.fs_util import FileSystemUtil +from metomi.rose.macro import add_meta_paths, load_meta_config +from metomi.rose.macros import DefaultValidators +from metomi.rose.opt_parse import RoseOptionParser +from metomi.rose.popen import RosePopener, RosePopenError +from metomi.rose.reporter import Event, Reporter +from metomi.rose.resource import ResourceLocator +from metomi.rosie.suite_id import (SuiteId, + SuiteIdOverflowError, + SuiteIdPrefixError) import shutil from io import StringIO import sys @@ -92,7 +94,7 @@ def __str__(self): class SuiteInfoError(Exception): """Raised when settings in rose-suite.info are invalid.""" def __str__(self): - return rose.macro.get_reports_as_text( + return metomi.rose.macro.get_reports_as_text( {None: self.args[0]}, "rose-suite.info") @@ -245,9 +247,9 @@ def create(self, info_config, from_id=None, prefix=None, meta_suite_mode=False): """Create a suite. - info_config -- A rose.config.ConfigNode object, which will be used as - the content of the "rose-suite.info" file of the new - suite. + info_config -- A metomi.rose.config.ConfigNode object, which will be + used as the content of the "rose-suite.info" file of the + new suite. from_id -- If defined, copy items from it. prefix -- If defined, create the suite in the suite repository named by the prefix instead of the default one. @@ -268,7 +270,7 @@ def create(self, info_config, from_id=None, prefix=None, self.popen("svn", "export", "-q", "--force", from_id_url, dir_) else: open(os.path.join(dir_, "rose-suite.conf"), "w").close() - rose.config.dump( + metomi.rose.config.dump( info_config, os.path.join(dir_, "rose-suite.info")) # Attempt to import the temporary suite to the repository @@ -332,12 +334,12 @@ def delete(self, id_, local_only=False): return id_ def generate_info_config(self, from_id=None, prefix=None, project=None): - """Generate a rose.config.ConfigNode for a rose-suite.info. + """Generate a metomi.rose.config.ConfigNode for a rose-suite.info. This is suitable for passing into the create method of this class. If from_id is defined, copy items from it. - Return the rose.config.ConfigNode instance. + Return the metomi.rose.config.ConfigNode instance. """ from_project = None @@ -347,11 +349,11 @@ def generate_info_config(self, from_id=None, prefix=None, project=None): from_id.branch, from_id.revision) out_data = self.popen("svn", "cat", from_info_url)[0] - from_config = rose.config.load(StringIO(out_data.decode())) + from_config = metomi.rose.config.load(StringIO(out_data.decode())) res_loc = ResourceLocator.default() older_config = None - info_config = rose.config.ConfigNode() + info_config = metomi.rose.config.ConfigNode() # Determine project if given as a command-line option on create if from_id is None and project is not None: @@ -360,7 +362,7 @@ def generate_info_config(self, from_id=None, prefix=None, project=None): # Set the compulsory fields and use the project and metadata if # available. meta_config = load_meta_config( - info_config, config_type=rose.INFO_CONFIG_NAME) + info_config, config_type=metomi.rose.INFO_CONFIG_NAME) if from_id is None and project is not None: for node_keys, node in meta_config.walk(no_ignore=True): if isinstance(node.value, dict): @@ -392,7 +394,8 @@ def generate_info_config(self, from_id=None, prefix=None, project=None): owner = res_loc.get_conf().get_value( ["rosie-id", "prefix-username." + prefix]) if not owner and self.subversion_servers_conf: - servers_conf = rose.config.load(self.subversion_servers_conf) + servers_conf = metomi.rose.config.load( + self.subversion_servers_conf) groups_node = servers_conf.get(["groups"]) if groups_node is not None: prefix_loc = SuiteId.get_prefix_location(prefix) @@ -446,7 +449,7 @@ def generate_info_config(self, from_id=None, prefix=None, project=None): reminder = ("please remove all commented hints/lines " + "in the main/top section before saving.") info_config.set([sect], - rose.variable.array_split(value)[0], + metomi.rose.variable.array_split(value)[0], comments=[value, reminder]) if older_config is not None: for node_keys, node in older_config.walk(no_ignore=True): @@ -463,7 +466,8 @@ def validate_info_config(cls, info_config): """Validate contents in suite info file.""" reports = DefaultValidators().validate( info_config, - load_meta_config(info_config, config_type=rose.INFO_CONFIG_NAME)) + load_meta_config(info_config, + config_type=metomi.rose.INFO_CONFIG_NAME)) if reports: raise SuiteInfoError(reports) @@ -493,7 +497,7 @@ def _copy1(self, info_config, from_id): dir_ = os.path.join(temp_local_copy, os.sep.join(new_id.sid)) self.popen( "svn", "cp", "-q", from_id_url, os.path.join(dir_, "trunk")) - rose.config.dump( + metomi.rose.config.dump( info_config, os.path.join(dir_, "trunk", "rose-suite.info")) message = self.COMMIT_MESSAGE_COPY % ( new_id, from_id.to_string_with_version()) @@ -560,7 +564,7 @@ def create(argv): meta_config = load_meta_config( info_config, directory=None, - config_type=rose.INFO_CONFIG_NAME, + config_type=metomi.rose.INFO_CONFIG_NAME, error_handler=None, ignore_meta_error=False) for node_keys, node in meta_config.walk(no_ignore=True): @@ -579,7 +583,7 @@ def create(argv): file_ = opts.info_file if opts.info_file == "-": file_ = sys.stdin - info_config = rose.config.load(file_) + info_config = metomi.rose.config.load(file_) info_config = _validate_info_config(opts, client, info_config) if interactive_mode: prefix = opts.prefix @@ -638,7 +642,7 @@ def _edit_info_config(opts, client, info_config): temp_desc, temp_name = mkstemp() try: temp_file = os.fdopen(temp_desc, "w") - rose.config.dump(info_config, temp_file) + metomi.rose.config.dump(info_config, temp_file) temp_file.write(CREATE_INFO_CONFIG_COMMENT) temp_file.close() command_list = client.popen.get_cmd("editor", temp_name) @@ -647,7 +651,7 @@ def _edit_info_config(opts, client, info_config): client.event_handler(exc) sys.exit(1) else: - return rose.config.load(temp_name) + return metomi.rose.config.load(temp_name) finally: os.unlink(temp_name) @@ -704,7 +708,7 @@ def main(): if argv[0] == name: return globals()[name](argv[1:]) else: - sys.exit("rosie.vc: %s: incorrect usage" % argv[0]) + sys.exit("metomi.rosie.vc: %s: incorrect usage" % argv[0]) if __name__ == "__main__": diff --git a/lib/python/rosie/ws.py b/metomi/rosie/ws.py similarity index 96% rename from lib/python/rosie/ws.py rename to metomi/rosie/ws.py index b97d3dd8cd..cf48722ba9 100644 --- a/lib/python/rosie/ws.py +++ b/metomi/rosie/ws.py @@ -40,17 +40,18 @@ import os import pwd import signal +import pkg_resources from time import sleep from tornado.ioloop import IOLoop, PeriodicCallback import tornado.log import tornado.web from isodatetime.data import get_timepoint_from_seconds_since_unix_epoch -from rose.host_select import HostSelector -from rose.opt_parse import RoseOptionParser -from rose.resource import ResourceLocator -import rosie.db -from rosie.suite_id import SuiteId +from metomi.rose.host_select import HostSelector +from metomi.rose.opt_parse import RoseOptionParser +from metomi.rose.resource import ResourceLocator +import metomi.rosie.db +from metomi.rosie.suite_id import SuiteId LOG_ROOT_TMPL = os.path.join( @@ -82,13 +83,19 @@ def __init__(self, service_root_mode=False, *args, **kwargs): self.props["host_name"] = ( self.props["host_name"].split(".", 1)[0]) self.props["rose_version"] = ResourceLocator.default().get_version() + + # Get location of HTML files from package + rosie_lib = os.path.join( + pkg_resources.resource_filename( + 'metomi.rosie', 'lib' + ), "html", "template", "rosie-disco" + ) + # Autoescape markup to prevent code injection from user inputs. self.props["template_env"] = jinja2.Environment( autoescape=jinja2.select_autoescape( enabled_extensions=("html", "xml"), default_for_string=True), - loader=jinja2.FileSystemLoader( - ResourceLocator.default().get_util_home( - "lib", "html", "template", "rosie-disco"))) + loader=jinja2.FileSystemLoader(rosie_lib)) db_url_map = {} for key, node in rose_conf.get(["rosie-db"]).value.items(): @@ -191,7 +198,7 @@ def initialize(self, props, prefix, db_url, service_root): self.source_url = "" if source_url_node is not None: self.source_url = source_url_node.value - self.dao = rosie.db.DAO(db_url) + self.dao = metomi.rosie.db.DAO(db_url) self.service_root = service_root[:-1] # remove the '?' regex aspect # Decorator to ensure there is a trailing slash since buttons for keys @@ -204,7 +211,7 @@ def get(self, *args): except (KeyError, AttributeError, jinja2.exceptions.TemplateError): import traceback traceback.print_exc() - except rosie.db.RosieDatabaseConnectError as exc: + except metomi.rosie.db.RosieDatabaseConnectError as exc: raise tornado.web.HTTPError(404, str(exc)) def _render(self, all_revs=0, data=None, filters=None, s=None): diff --git a/lib/python/rosie/ws_client.py b/metomi/rosie/ws_client.py similarity index 97% rename from lib/python/rosie/ws_client.py rename to metomi/rosie/ws_client.py index 5aa4ebd83e..b7d3748cb1 100644 --- a/lib/python/rosie/ws_client.py +++ b/metomi/rosie/ws_client.py @@ -31,11 +31,11 @@ import shlex from time import sleep -from rose.popen import RosePopener -from rose.reporter import Reporter -from rose.resource import ResourceLocator -from rosie.suite_id import SuiteId -from rosie.ws_client_auth import RosieWSClientAuthManager +from metomi.rose.popen import RosePopener +from metomi.rose.reporter import Reporter +from metomi.rose.resource import ResourceLocator +from metomi.rosie.suite_id import SuiteId +from metomi.rosie.ws_client_auth import RosieWSClientAuthManager class RosieWSClientConfError(Exception): @@ -43,7 +43,9 @@ class RosieWSClientConfError(Exception): """Raised if no Rosie service server is configured.""" def __str__(self): - return "[rosie-id] settings not defined in site/user configuration." + msg = ("[metomi.rosie-id] settings not defined in site/user" + " configuration.") + return msg class RosieWSClientError(Exception): diff --git a/lib/python/rosie/ws_client_auth.py b/metomi/rosie/ws_client_auth.py similarity index 94% rename from lib/python/rosie/ws_client_auth.py rename to metomi/rosie/ws_client_auth.py index 89be3d4ff4..b7e2fc0ed9 100644 --- a/lib/python/rosie/ws_client_auth.py +++ b/metomi/rosie/ws_client_auth.py @@ -18,27 +18,7 @@ # along with Rose. If not, see . # ----------------------------------------------------------------------------- """The authentication manager for the Rosie web service client.""" - -# This try/except loop attempts to load the 'Secret' object. Running on -# systems with GTK+ >=v3 this should work: Systems on GTK =v3 use PyGObject; v2 use PyGTK - require_version('Secret', '1') - from gi.repository import Secret - del pygtkcompat - GI_FLAG = True -except (ImportError, ValueError): - GI_FLAG = False -try: - with warnings.catch_warnings(): - warnings.simplefilter('ignore') - import gtk - import gnomekeyring -except ImportError: - pass +GI_FLAG = False import ast from getpass import getpass @@ -48,11 +28,11 @@ import sys from urllib.parse import urlparse -import rose.config -from rose.env import env_var_process -from rose.popen import RosePopener -from rose.reporter import Reporter -from rose.resource import ResourceLocator +import metomi.rose.config +from metomi.rose.env import env_var_process +from metomi.rose.popen import RosePopener +from metomi.rose.reporter import Reporter +from metomi.rose.resource import ResourceLocator import socket @@ -400,12 +380,12 @@ def store_password(self): user_rose_conf_path = os.path.join(ResourceLocator.USER_CONF_PATH, ResourceLocator.ROSE_CONF) if os.access(user_rose_conf_path, os.F_OK | os.R_OK | os.W_OK): - config = rose.config.load(user_rose_conf_path) + config = metomi.rose.config.load(user_rose_conf_path) else: - config = rose.config.ConfigNode() + config = metomi.rose.config.ConfigNode() config.set( ["rosie-id", "prefix-username." + self.prefix], self.username) - rose.config.dump(config, user_rose_conf_path) + metomi.rose.config.dump(config, user_rose_conf_path) if (self.password_store is not None and self.password and self.password_orig != self.password): self.password_store.store_password( diff --git a/lib/python/rosie/ws_client_cli.py b/metomi/rosie/ws_client_cli.py similarity index 96% rename from lib/python/rosie/ws_client_cli.py rename to metomi/rosie/ws_client_cli.py index eacc4179ec..db2df6d5a0 100644 --- a/lib/python/rosie/ws_client_cli.py +++ b/metomi/rosie/ws_client_cli.py @@ -21,13 +21,13 @@ import re -from rosie.suite_id import SuiteId -from rosie.ws_client import ( +from metomi.rosie.suite_id import SuiteId +from metomi.rosie.ws_client import ( RosieWSClient, RosieWSClientError, RosieWSClientConfError) -from rosie.ws_client_auth import UndefinedRosiePrefixWS -from rose.opt_parse import RoseOptionParser -from rose.popen import RosePopenError -from rose.reporter import Reporter, Event +from metomi.rosie.ws_client_auth import UndefinedRosiePrefixWS +from metomi.rose.opt_parse import RoseOptionParser +from metomi.rose.popen import RosePopenError +from metomi.rose.reporter import Reporter, Event import sys import time import traceback @@ -264,7 +264,7 @@ def main(): try: func = globals()[argv[0]] # Potentially bad. except KeyError: - sys.exit("rosie.ws_client_cli: %s: incorrect usage" % argv[0]) + sys.exit("metomi.rosie.ws_client_cli: %s: incorrect usage" % argv[0]) try: sys.exit(func(argv[1:])) except KeyboardInterrupt: diff --git a/pytest.ini b/pytest.ini index 2827891c9c..75e277c38c 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,13 +1,13 @@ [pytest] addopts = --verbose --doctest-modules - --ignore=lib/python/isodatetime - --ignore=lib/python/rosie - --ignore=lib/python/rose/ws.py - --ignore=lib/python/rose/metadata_graph.py + --ignore=metomi/isodatetime + --ignore=metomi/rosie + --ignore=metomi/rose/ws.py + --ignore=metomi/rose/metadata_graph.py # these tests do IO, don't run them under sphinx-build rather than pytest: - --ignore=lib/python/rose/config.py - --ignore=lib/python/rose/macro.py + --ignore=metomi/rose/config.py + --ignore=metomi/rose/macro.py testpaths = - lib/ + metomi/rose/tests/* sphinx/ diff --git a/sbin/rosa-db-create b/sbin/rosa-db-create index 5ee017c5ce..ee6617a7a4 100755 --- a/sbin/rosa-db-create +++ b/sbin/rosa-db-create @@ -30,4 +30,4 @@ # determine the list of database files to create. # Does not override existing database files. #------------------------------------------------------------------------------- -exec python3 -m rosie.db_create "$@" +exec python3 -m metomi.rosie.db_create "$@" diff --git a/sbin/rosa-svn-post-commit b/sbin/rosa-svn-post-commit index 612ce32309..72b56133b0 100755 --- a/sbin/rosa-svn-post-commit +++ b/sbin/rosa-svn-post-commit @@ -26,4 +26,4 @@ # DESCRIPTION # Update the Rosie discovery database for an SVN changeset. #------------------------------------------------------------------------------- -exec python3 -m rosie.svn_post_commit "$@" +exec python3 -m metomi.rosie.svn_post_commit "$@" diff --git a/sbin/rosa-svn-pre-commit b/sbin/rosa-svn-pre-commit index ec87ffa4ea..1231de67e9 100755 --- a/sbin/rosa-svn-pre-commit +++ b/sbin/rosa-svn-pre-commit @@ -26,4 +26,4 @@ # DESCRIPTION # Ensure that an SVN commit conforms to the rules of Rosie. #------------------------------------------------------------------------------- -exec python3 -m rosie.svn_pre_commit "$@" +exec python3 -m metomi.rosie.svn_pre_commit "$@" diff --git a/sbin/rosa-ws b/sbin/rosa-ws index dc40f27bb8..d865e2477a 100755 --- a/sbin/rosa-ws +++ b/sbin/rosa-ws @@ -32,4 +32,4 @@ # # This command is deprecated. Use "rosie disco" instead. #------------------------------------------------------------------------------- -exec python3 -m rosie.ws "$@" +exec python3 -m metomi.rosie.ws "$@" diff --git a/setup.py b/setup.py new file mode 100644 index 0000000000..66c56e59c0 --- /dev/null +++ b/setup.py @@ -0,0 +1,96 @@ +#!/usr/ bin/env python3 +# coding=utf-8 + +# Copyright (C) 2012-2019 British Crown (Met Office) & Contributors. +# +# This file is part of Rose, a framework for meteorological suites. +# +# Rose is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Rose is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Rose. If not, see . + +import codecs + +from glob import glob +from os.path import join, abspath, dirname +import re + + +from setuptools import setup, find_namespace_packages + +here = abspath(dirname(__file__)) + + +def read(*parts): + with codecs.open(join(here, *parts), "r") as fp: + return fp.read() + + +def find_version(*file_paths): + version_file = read(*file_paths) + version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", + version_file, re.M) + if version_match: + return version_match.group(1) + raise RuntimeError("Unable to find version string.") + + +with open("README.md", "r") as fh: + long_description = fh.read() + + +INSTALL_REQUIRES = [ + "jinja2>=2.10.1, <2.11.0", + "cylc-flow==8.0a0", + "aiofiles", + "tornado", + "sqlalchemy", + "isodatetime", + "requests", + "ldap3", +] + + +setup( + # Metadata + name="metomi-rose", + description="Rose, a framework for meteorological suites.", + long_description=long_description, + long_description_content_type="text/markdown", + version=find_version("metomi", "rose", "__init__.py"), + author="2012-2019 British Crown (Met Office) & Contributors", + author_email="metomi@metoffice.gov.uk", + license='GPL', + license_file="https://metomi.github.io/rose/doc/html/terms.html", + url="https://metomi.github.io/rose/doc/html/index.html", + platforms="any", + classifiers="""Environment :: Console +Environment :: Web Environment +Intended Audience :: Developers +Intended Audience :: System Administrators +Intended Audience :: Science/Research +Operating System :: POSIX :: Linux +Programming Language :: Python :: 3.7 +Topic :: Scientific/Engineering :: Atmospheric Science""", + + # Options + scripts=glob(join("bin", "*")) + + glob(join("sbin", "*")) + + glob(join("lib", "bash", "*")), + install_requires=INSTALL_REQUIRES, + python_requires=">=3.7", + package_data={ + "metomi.rose": ["etc/.*"], + "metomi.rosie": ["lib/*"] + }, + packages=find_namespace_packages(include=["metomi.*"]), +) diff --git a/sphinx/api/built-in/rose_ana.rst b/sphinx/api/built-in/rose_ana.rst index afd7673ee9..d372e99c79 100644 --- a/sphinx/api/built-in/rose_ana.rst +++ b/sphinx/api/built-in/rose_ana.rst @@ -112,13 +112,13 @@ There is one built-in module of analysis classes called ``grepper``. .. To document everything: - .. automodule:: rose.apps.ana_builtin.grepper + .. automodule:: metomi.rose.apps.ana_builtin.grepper :members: -.. autoclass:: rose.apps.ana_builtin.grepper.FileCommandPattern +.. autoclass:: metomi.rose.apps.ana_builtin.grepper.FileCommandPattern -.. autoclass:: rose.apps.ana_builtin.grepper.FilePattern +.. autoclass:: metomi.rose.apps.ana_builtin.grepper.FilePattern -.. autoclass:: rose.apps.ana_builtin.grepper.SingleCommandPattern +.. autoclass:: metomi.rose.apps.ana_builtin.grepper.SingleCommandPattern -.. autoclass:: rose.apps.ana_builtin.grepper.SingleCommandStatus +.. autoclass:: metomi.rose.apps.ana_builtin.grepper.SingleCommandStatus diff --git a/sphinx/api/command-reference.rst b/sphinx/api/command-reference.rst index 78bc659149..7ba8f85c0d 100644 --- a/sphinx/api/command-reference.rst +++ b/sphinx/api/command-reference.rst @@ -9,6 +9,59 @@ Rose Commands .. auto-cli-doc:: rose rose +---- + +.. _command-rose-test-battery: + +etc/bin/rose-test-battery +^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: bash + + $ROSE_DEVELOPER_DIR/etc/bin/rose-test-battery + +Run Rose self tests. + +n.b. This will only work where a developer has downloaded and installed +their own copy of Rose using pip install -e . + +Change directory to Rose source tree, and runs this shell command: + +``exec prove -j "$NPROC" -s -r "${@:-t}"`` + +where ``NPROC`` is the number of processors on your computer (or the +setting ``[t]prove-options`` in the site/user configuration file). If you +do not want to run the full test suite, you can specify the names of +individual test files or their containing directories as extra arguments. + +**EXAMPLES** + +.. code-block:: bash + + # Run the full test suite with the default options. + rose test-battery + # Run the full test suite with 12 processes. + rose test-battery -j 12 + # Run only tests under "t/rose-app-run/" with 12 processes. + rose test-battery -j 12 t/rose-app-run + # Run only "t/rose-app-run/07-opt.t" in verbose mode. + rose test-battery -v t/rose-app-run/07-opt.t + +**SEE ALSO** + +* ``prove(1)``\ + +---- + +.. _command-rose-make-docs: + +etc/bin/rose-make-docs +^^^^^^^^^^^^^^^^^^^^^^ + +stuf and nonsens + +---- + Rosie Commands -------------- diff --git a/sphinx/api/configuration/api.rst b/sphinx/api/configuration/api.rst index 152973bd0a..1206e66cc7 100644 --- a/sphinx/api/configuration/api.rst +++ b/sphinx/api/configuration/api.rst @@ -31,5 +31,5 @@ Rose provides a Python API for loading, processing, editing and dumping Rose configurations via the :py:mod:`rose.config` module located within the Rose Python library. -.. automodule:: rose.config +.. automodule:: metomi.rose.config :members: diff --git a/sphinx/api/rose-macro.rst b/sphinx/api/rose-macro.rst index 06818de25c..24c0b780cf 100644 --- a/sphinx/api/rose-macro.rst +++ b/sphinx/api/rose-macro.rst @@ -82,7 +82,7 @@ A validator macro should look like: .. code-block:: python - import rose.macro + import metomi.rose.macro class SomeValidator(rose.macro.MacroBase): @@ -140,7 +140,7 @@ A transformer macro should look like: .. code-block:: python - import rose.macro + import metomi.rose.macro class SomeTransformer(rose.macro.MacroBase): @@ -220,5 +220,5 @@ set to the name of the optional configuration the macro is being run on, or Python API ---------- -.. automodule:: rose.macro +.. automodule:: metomi.rose.macro :members: MacroBase, MacroReport diff --git a/sphinx/api/rose-upgrader-macros.rst b/sphinx/api/rose-upgrader-macros.rst index 034a63a52f..638ef99b0d 100644 --- a/sphinx/api/rose-upgrader-macros.rst +++ b/sphinx/api/rose-upgrader-macros.rst @@ -55,5 +55,5 @@ file - ``rose-meta/CATEGORY/versions.py``. Upgrade macros are subclasses of :py:class:`rose.upgrade.MacroUpgrade`. They have all the functionality of the :ref:`transformer macros `. -.. autoclass:: rose.upgrade.MacroUpgrade +.. autoclass:: metomi.rose.upgrade.MacroUpgrade :members: diff --git a/sphinx/conf.py b/sphinx/conf.py index 5e51f4d742..f59f18ebef 100644 --- a/sphinx/conf.py +++ b/sphinx/conf.py @@ -20,8 +20,8 @@ import sys import os -from rose.popen import RosePopener -from rose.resource import ResourceLocator +import metomi.rose +from metomi.rose.popen import RosePopener # rose-documentation build configuration file, initial version created by # sphinx-quickstart. @@ -102,7 +102,7 @@ # The full version for the project you're documenting, acts as replacement for # |version|. -release = ResourceLocator().get_version(ignore_environment=True) +release = metomi.rose.__version__ # The short X.Y version, acts as replacement for |release|. version = release diff --git a/sphinx/developing/autodoc.rst b/sphinx/developing/autodoc.rst index 326c5f0516..b00bb0d8e7 100644 --- a/sphinx/developing/autodoc.rst +++ b/sphinx/developing/autodoc.rst @@ -87,7 +87,7 @@ values will have to be provided and should sit on the next newline. .. code-block:: python - >>> import rose.config + >>> import metomi.rose.config >>> rose.config.ConfigNode() {'state': '', 'comments': [], 'value': {}} diff --git a/sphinx/developing/domains.rst b/sphinx/developing/domains.rst index 56309bc4c0..72b54a02d6 100644 --- a/sphinx/developing/domains.rst +++ b/sphinx/developing/domains.rst @@ -30,11 +30,11 @@ Rose Domain .. autoclass:: rose_domain.RoseDirective :members: NAME, LABEL, ARGUMENT_SEPARATOR, ALT_FORM_SEPARATOR, ALT_FORM_TEMPLATE - + .. autoclass:: rose_domain.RoseAppDirective .. autoclass:: rose_domain.RoseFileDirective - + .. autoclass:: rose_domain.RoseConfigDirective .. autoclass:: rose_domain.RoseAutoDirective diff --git a/sphinx/ext/rose_domain.py b/sphinx/ext/rose_domain.py index caa4d3856e..e13a27d3c9 100644 --- a/sphinx/ext/rose_domain.py +++ b/sphinx/ext/rose_domain.py @@ -134,7 +134,7 @@ from sphinx.util import logging from sphinx.util.docfields import Field, TypedField -from rose import config +from metomi.rose import config LOGGER = logging.getLogger(__name__) diff --git a/sphinx/installation.rst b/sphinx/installation.rst index 053b9024be..90f50d45a4 100644 --- a/sphinx/installation.rst +++ b/sphinx/installation.rst @@ -10,8 +10,9 @@ Installation The source code for Rose is available via `GitHub`_, code releases are available in ``zip`` and ``tar.gz`` `archives`_. -1. Un-pack the archive file into an appropriate location on your system. -2. Add the ``rose/bin/`` directory into your ``PATH`` environment variable. +1. If you wish to do so create a new environment using Conda, Venv or some + other environment manager. +2. Install using ``pip install metomi-rose`` 3. Check system compatibility by running :ref:`command-rose-check-software`. @@ -70,7 +71,7 @@ Hosts For Running User Interactive Tools Installation requirements: * Rose, Cylc, Bash, Python, requests, Subversion, FCM, - Pygraphviz (+ graphviz), PyGTK (+ GTK). + Pygraphviz (+ graphviz). Connectivity requirements: * Must have HTTP access to the hosts running the Rosie web service. @@ -84,7 +85,7 @@ Hosts For Running Rose Bush ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Installation requirements: - * Rose, Bash, Python, cherrypy, jinja2. + * Rose, Bash, Python, jinja2. Connectivity requirements: * Must be able to access the home directories of users' Cylc run directories. @@ -96,7 +97,7 @@ Typically you will only need a single host but you can have multiple repositories on different hosts if you require this. Installation requirements: - * Rose, Bash, Python, cherrypy, jinja2, sqlalchemy, Subversion. + * Rose, Bash, Python, jinja2, sqlalchemy, Subversion. .. note:: @@ -112,25 +113,6 @@ Installation requirements: Configuring Rose ---------------- -``lib/bash/rose_init_site`` - If you do not have root access to install the required Python libraries, - you may need to edit this file to ensure Python libraries are included - in the ``PYTHONPATH`` environment variable. For example, if you have - installed some essential Python libraries in your ``HOME`` directory, - you can add a ``lib/bash/rose_init_site`` file with the following - contents: - - .. code-block:: bash - - # Essential Python libraries installed under - # "/home/daisy/usr/lib/python2.6/site-packages/" - if [[ -n "${PYTHONPATH:-}" ]]; then - PYTHONPATH="/home/daisy/usr/lib/python2.6/site-packages:${PYTHONPATH}" - else - PYTHONPATH="/home/daisy/usr/lib/python2.6/site-packages" - fi - export PYTHONPATH - ``etc/rose.conf`` You should add/edit this file to meet the requirement of your site. Examples can be found at the ``etc/rose.conf.example`` file in your @@ -268,12 +250,7 @@ You can start an ad-hoc Rose Bush web server by running:: You will find the access and error logs under ``~/.metomi/rose-bush*``. Alternatively you can run the Rose Bush web service under Apache -``mod_wsgi``. To do this you will need to set up an Apache module -configuration file (typically in ``/etc/httpd/conf.d/rose-wsgi.conf``) -containing the following (with the paths set appropriately):: - - WSGIPythonPath /path/to/rose/lib/python - WSGIScriptAlias /rose-bush /path/to/rose/lib/python/rose/bush.py +``mod_wsgi``. Use the Apache log at e.g. ``/var/log/httpd/`` to debug problems. See also `Configuring a Rosie Server`_. diff --git a/sphinx/tutorial/rose/furthertopics/failif-warnif.rst b/sphinx/tutorial/rose/furthertopics/failif-warnif.rst index 16a48746f6..4ad9d63bae 100644 --- a/sphinx/tutorial/rose/furthertopics/failif-warnif.rst +++ b/sphinx/tutorial/rose/furthertopics/failif-warnif.rst @@ -80,7 +80,7 @@ looks like this: fuelless_weight_kg=2353.0 specific_impulse_s=311.0 -.. image:: http://upload.wikimedia.org/wikipedia/commons/thumb/2/2a/Apollo16LM.jpg/533px-Apollo16LM.jpg +.. image:: https://upload.wikimedia.org/wikipedia/commons/thumb/2/2a/Apollo16LM.jpg/533px-Apollo16LM.jpg :align: right :alt: Apollo 11 Lunar Module, returning from the surface of the Moon :width: 250px diff --git a/sphinx/tutorial/rose/furthertopics/macro-development.rst b/sphinx/tutorial/rose/furthertopics/macro-development.rst index 54340a6929..745240f531 100644 --- a/sphinx/tutorial/rose/furthertopics/macro-development.rst +++ b/sphinx/tutorial/rose/furthertopics/macro-development.rst @@ -96,7 +96,7 @@ Open ``planet.py`` in a text editor and paste in the following code: import re import subprocess - import rose.macro + import metomi.rose.macro class PlanetChecker(rose.macro.MacroBase): @@ -213,7 +213,7 @@ Your final macro should look like this: import re import subprocess - import rose.macro + import metomi.rose.macro class PlanetChecker(rose.macro.MacroBase): diff --git a/sphinx/tutorial/rose/furthertopics/rose-stem-tutorial.rst b/sphinx/tutorial/rose/furthertopics/rose-stem-tutorial.rst index cf4bb51ffc..c2a607a5cd 100644 --- a/sphinx/tutorial/rose/furthertopics/rose-stem-tutorial.rst +++ b/sphinx/tutorial/rose/furthertopics/rose-stem-tutorial.rst @@ -11,7 +11,7 @@ Rose Stem Tutorial Before proceeding you should already be familiar with the :ref:`Rose Stem` section. -.. image:: http://upload.wikimedia.org/wikipedia/commons/thumb/b/b2/Cassini_Saturn_Orbit_Insertion.jpg/320px-Cassini_Saturn_Orbit_Insertion.jpg +.. image:: https://upload.wikimedia.org/wikipedia/commons/thumb/b/b2/Cassini_Saturn_Orbit_Insertion.jpg/320px-Cassini_Saturn_Orbit_Insertion.jpg :align: right :alt: Artist's Impression of Cassini entering Saturn orbit :width: 300px diff --git a/sphinx/tutorial/rose/furthertopics/rose-stem.rst b/sphinx/tutorial/rose/furthertopics/rose-stem.rst index 9f131d9456..fc1735e58c 100644 --- a/sphinx/tutorial/rose/furthertopics/rose-stem.rst +++ b/sphinx/tutorial/rose/furthertopics/rose-stem.rst @@ -230,19 +230,19 @@ The only analysis module provided with Rose is :py:mod:`rose.apps.ana_builtin.grepper`, it provides the following analysis tasks and options: -.. autoclass:: rose.apps.ana_builtin.grepper.SingleCommandStatus +.. autoclass:: metomi.rose.apps.ana_builtin.grepper.SingleCommandStatus :noindex: -.. autoclass:: rose.apps.ana_builtin.grepper.SingleCommandPattern +.. autoclass:: metomi.rose.apps.ana_builtin.grepper.SingleCommandPattern :noindex: -.. autoclass:: rose.apps.ana_builtin.grepper.FilePattern +.. autoclass:: metomi.rose.apps.ana_builtin.grepper.FilePattern :noindex: -.. autoclass:: rose.apps.ana_builtin.grepper.FileCommandPattern +.. autoclass:: metomi.rose.apps.ana_builtin.grepper.FileCommandPattern :noindex: -.. automodule:: rose.apps.ana_builtin.grepper +.. automodule:: metomi.rose.apps.ana_builtin.grepper :noindex: The format for analysis modules themselves is relatively simple; the easiest @@ -252,13 +252,13 @@ recognised as a valid analysis module, the Python file must contain at least one class which inherits and extends :py:mod:`rose.apps.rose_ana.AnalysisTask`: -.. autoclass:: rose.apps.rose_ana.AnalysisTask +.. autoclass:: metomi.rose.apps.rose_ana.AnalysisTask For example: .. code-block:: python - from rose.apps.rose_ana import AnalysisTask + from metomi.rose.apps.rose_ana import AnalysisTask class CustomAnalysisTask(AnalysisTask): """My new custom analysis task.""" diff --git a/sphinx/tutorial/rose/furthertopics/upgrading-macro-development.rst b/sphinx/tutorial/rose/furthertopics/upgrading-macro-development.rst index 9b0fac7b41..ed3863c800 100644 --- a/sphinx/tutorial/rose/furthertopics/upgrading-macro-development.rst +++ b/sphinx/tutorial/rose/furthertopics/upgrading-macro-development.rst @@ -13,7 +13,7 @@ in the reference material). Example ------- -.. image:: http://upload.wikimedia.org/wikipedia/commons/b/b9/Proa1.jpg +.. image:: https://upload.wikimedia.org/wikipedia/commons/b/b9/Proa1.jpg :align: right :width: 250px @@ -234,7 +234,7 @@ Paste the following into your ``versions.py`` file: .. code-block:: python - import rose.upgrade + import metomi.rose.upgrade class MyFirstUpgradeMacro(rose.upgrade.MacroUpgrade): diff --git a/t/docs/01-doctest.t b/t/docs/01-doctest.t index 75de328ac0..2771cc399c 100644 --- a/t/docs/01-doctest.t +++ b/t/docs/01-doctest.t @@ -19,13 +19,14 @@ #------------------------------------------------------------------------------- # Run doctests in sphinx extensions. #------------------------------------------------------------------------------- +THIS_TEST_HOME=$(pwd) . "$(dirname "$0")/test_header" #------------------------------------------------------------------------------- if ! rose check-software --docs 2>'/dev/null'; then skip_all "Software dependencies for documentation not met." fi #------------------------------------------------------------------------------- -FILES=($(find "${ROSE_HOME}/sphinx/ext" -name "*.py")) +FILES=($(find "${THIS_TEST_HOME}/sphinx/ext" -name "*.py")) tests $(( ${#FILES[@]} * 2 )) #------------------------------------------------------------------------------- TERM= # Nasty solution to prevent control chars being printed in python output. diff --git a/t/docs/02-tutorial-suites.t b/t/docs/02-tutorial-suites.t index d6fd6bbb63..bbdbfa9c07 100644 --- a/t/docs/02-tutorial-suites.t +++ b/t/docs/02-tutorial-suites.t @@ -30,7 +30,7 @@ TEST_KEYS=('') TUT_DIRS=('') TESTS=('') -TUTORIALS_PATH="${ROSE_HOME}/etc/tutorial" +TUTORIALS_PATH="${ROSE_TEST_HOME}/etc/tutorial" for tutorial in $(ls -1 "${TUTORIALS_PATH}"); do tutorial_path="${TUTORIALS_PATH}/${tutorial}" validate_file="${tutorial_path}/.validate" diff --git a/t/lib/bash/test_header b/t/lib/bash/test_header index 7b57376d88..66eeca0e27 100644 --- a/t/lib/bash/test_header +++ b/t/lib/bash/test_header @@ -440,10 +440,8 @@ __PYTHON__ fi } -ROSE_HOME="${ROSE_HOME:-"$(\ - cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"}" -PATH=$ROSE_HOME/bin:$PATH -# shellcheck disable=SC2034 +ROSE_TEST_HOME=$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd) +export ROSE_TEST_HOME DIFFTOOL="$(rose config '--default=diff -u' t difftool)" TEST_KEY_BASE="$(basename "$0" .t)" # shellcheck disable=SC2034 diff --git a/t/rosa-db-create/00-basic.t b/t/rosa-db-create/00-basic.t index a6eae900e7..a9d497a215 100755 --- a/t/rosa-db-create/00-basic.t +++ b/t/rosa-db-create/00-basic.t @@ -22,6 +22,7 @@ # svn-post-commit", which is tested quite thoroughly in its own test suite. #------------------------------------------------------------------------------- . $(dirname $0)/test_header + #------------------------------------------------------------------------------- if ! python3 -c 'import sqlalchemy' 2>/dev/null; then skip_all '"sqlalchemy" not installed' @@ -46,7 +47,7 @@ export ROSE_CONF_PATH=$PWD/conf cat >repos/foo/hooks/post-commit <<__POST_COMMIT__ #!/bin/bash export ROSE_CONF_PATH=$ROSE_CONF_PATH -$ROSE_HOME/sbin/rosa svn-post-commit --debug "\$@" \\ +rosa svn-post-commit --debug "\$@" \\ 1>$PWD/rosa-svn-post-commit.out 2>$PWD/rosa-svn-post-commit.err echo \$? >$PWD/rosa-svn-post-commit.rc __POST_COMMIT__ @@ -54,7 +55,7 @@ chmod +x repos/foo/hooks/post-commit export LANG=C #------------------------------------------------------------------------------- TEST_KEY="$TEST_KEY_BASE-0" -run_pass "$TEST_KEY" $ROSE_HOME/sbin/rosa db-create +run_pass "$TEST_KEY" rosa db-create file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__ [INFO] sqlite:///$PWD/repos/foo.db: DB created. __OUT__ @@ -73,7 +74,7 @@ __ROSE_SUITE_INFO rosie create -q -y --info-file=rose-suite.info --no-checkout || exit 1 echo "2009-02-13T23:31:30.000000Z" >foo-date-1.txt svnadmin setrevprop $PWD/repos/foo -r 1 svn:date foo-date-1.txt -run_pass "$TEST_KEY" $ROSE_HOME/sbin/rosa db-create +run_pass "$TEST_KEY" rosa db-create file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__ [INFO] sqlite:///$PWD/repos/foo.db: DB created. [INFO] $PWD/repos/foo: DB loaded, r1 of 1. @@ -105,7 +106,7 @@ cp rose-suite.info $PWD/roses/foo-aa001/ svn update -q) || exit 1 echo "2009-02-13T23:31:32.000000Z" >foo-date-3.txt svnadmin setrevprop $PWD/repos/foo -r 3 svn:date foo-date-3.txt -run_pass "$TEST_KEY" $ROSE_HOME/sbin/rosa db-create +run_pass "$TEST_KEY" rosa db-create file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__ [INFO] sqlite:///$PWD/repos/foo.db: DB created. [INFO] $PWD/repos/foo: DB loaded, r3 of 3. @@ -120,7 +121,7 @@ rm $PWD/repos/foo.db rosie delete -q -y foo-aa000 || exit 1 echo "2009-02-13T23:31:33.000000Z" >foo-date-4.txt svnadmin setrevprop $PWD/repos/foo -r 4 svn:date foo-date-4.txt -run_pass "$TEST_KEY" $ROSE_HOME/sbin/rosa db-create +run_pass "$TEST_KEY" rosa db-create file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__ [INFO] sqlite:///$PWD/repos/foo.db: DB created. [INFO] $PWD/repos/foo: DB loaded, r4 of 4. diff --git a/t/rosa-svn-post-commit/00-basic.t b/t/rosa-svn-post-commit/00-basic.t index cee1dde32e..232cbeb897 100755 --- a/t/rosa-svn-post-commit/00-basic.t +++ b/t/rosa-svn-post-commit/00-basic.t @@ -20,6 +20,7 @@ # Test "rosa svn-post-commit": Rosie WS DB update. #------------------------------------------------------------------------------- . $(dirname $0)/test_header + #------------------------------------------------------------------------------- if ! python3 -c 'import sqlalchemy' 2>/dev/null; then skip_all '"sqlalchemy" not installed' @@ -41,16 +42,18 @@ prefix-owner-default.foo=fred prefix-location.foo=$SVN_URL __ROSE_CONF__ export ROSE_CONF_PATH=$PWD/conf +ROSE_BIN_HOME=$(dirname $(command -v rose)) cat >repos/foo/hooks/post-commit <<__POST_COMMIT__ #!/bin/bash -export ROSE_CONF_PATH=$ROSE_CONF_PATH -$ROSE_HOME/sbin/rosa svn-post-commit --debug "\$@" \\ +export ROSE_CONF_PATH=${ROSE_CONF_PATH} +export PATH=$PATH:${ROSE_BIN_HOME} +rosa svn-post-commit --debug "\$@" \\ 1>$PWD/rosa-svn-post-commit.out 2>$PWD/rosa-svn-post-commit.err echo \$? >$PWD/rosa-svn-post-commit.rc __POST_COMMIT__ chmod +x repos/foo/hooks/post-commit export LANG=C -$ROSE_HOME/sbin/rosa db-create -q || exit 1 +rosa db-create -q || exit 1 Q_LATEST='SELECT * FROM latest' Q_MAIN='SELECT idx,branch,revision,owner,project,title,author,status,from_idx FROM main' Q_META='SELECT * FROM meta' diff --git a/t/rosa-svn-post-commit/01-mail-passwd.t b/t/rosa-svn-post-commit/01-mail-passwd.t index cb786f25ec..2e7e3875af 100755 --- a/t/rosa-svn-post-commit/01-mail-passwd.t +++ b/t/rosa-svn-post-commit/01-mail-passwd.t @@ -20,6 +20,7 @@ # Test "rosa svn-post-commit": notification, user-tool=passwd. #------------------------------------------------------------------------------- . $(dirname $0)/test_header + if ! python3 -c 'import sqlalchemy' 2>/dev/null; then skip_all '"sqlalchemy" not installed' fi @@ -70,16 +71,18 @@ prefix-location.foo=$SVN_URL __CONF__ export ROSE_CONF_PATH=$PWD/conf +ROSE_BIN_HOME=$(dirname $(command -v rose)) cat >repos/foo/hooks/post-commit <<__POST_COMMIT__ #!/bin/bash export ROSE_CONF_PATH=$ROSE_CONF_PATH -$ROSE_HOME/sbin/rosa svn-post-commit --debug "\$@" \\ +export PATH=$PATH:${ROSE_BIN_HOME} +rosa svn-post-commit --debug "\$@" \\ 1>$PWD/rosa-svn-post-commit.out 2>$PWD/rosa-svn-post-commit.err echo \$? >$PWD/rosa-svn-post-commit.rc __POST_COMMIT__ chmod +x repos/foo/hooks/post-commit export LANG=C -$ROSE_HOME/sbin/rosa db-create -q +rosa db-create -q tests 30 #------------------------------------------------------------------------------- diff --git a/t/rosa-svn-post-commit/03-unicode.t b/t/rosa-svn-post-commit/03-unicode.t index f30f95cc59..328013ee5a 100755 --- a/t/rosa-svn-post-commit/03-unicode.t +++ b/t/rosa-svn-post-commit/03-unicode.t @@ -20,6 +20,7 @@ # Test "rosa svn-post-commit": Discovery Service database update with unicode. #------------------------------------------------------------------------------- . "$(dirname "$0")/test_header" + #------------------------------------------------------------------------------- if ! python3 -c 'import sqlalchemy' 2>'/dev/null'; then skip_all '"sqlalchemy" not installed' @@ -42,16 +43,18 @@ prefix-owner-default.foo=fred prefix-location.foo=${SVN_URL} __ROSE_CONF__ export ROSE_CONF_PATH="${PWD}/conf" +ROSE_BIN_HOME=$(dirname $(command -v rose)) cat >'repos/foo/hooks/post-commit' <<__POST_COMMIT__ #!/bin/bash export ROSE_CONF_PATH="${ROSE_CONF_PATH}" -'${ROSE_HOME}/sbin/rosa' 'svn-post-commit' --debug "\$@" \\ +export PATH=$PATH:${ROSE_BIN_HOME} +rosa svn-post-commit --debug "\$@" \\ 1>'${PWD}/rosa-svn-post-commit.out' 2>'${PWD}/rosa-svn-post-commit.err' echo "\$?" >'${PWD}/rosa-svn-post-commit.rc' __POST_COMMIT__ chmod +x 'repos/foo/hooks/post-commit' export LANG='C' -"${ROSE_HOME}/sbin/rosa" 'db-create' -q +rosa 'db-create' -q set +e Q_MAIN='SELECT idx,branch,revision,owner,project,title,author,status,from_idx FROM main' diff --git a/t/rosa-svn-pre-commit/00-basic.t b/t/rosa-svn-pre-commit/00-basic.t index b810ecd13b..94654febe7 100755 --- a/t/rosa-svn-pre-commit/00-basic.t +++ b/t/rosa-svn-pre-commit/00-basic.t @@ -20,6 +20,7 @@ # Basic tests for "rosa svn-pre-commit". #------------------------------------------------------------------------------- . $(dirname $0)/test_header + export ROSE_CONF_PATH= mkdir conf cat >conf/rose.conf <<'__ROSE_CONF__' @@ -32,10 +33,15 @@ tests 117 mkdir repos svnadmin create repos/foo SVN_URL=file://$PWD/repos/foo +ROSE_BIN=$(dirname $(command -v rose)) +ROSE_LIB=$(dirname $(python -c "import metomi.rose; print(metomi.rose.__file__)")) +export ROSE_LIB ROSE_BIN cat >repos/foo/hooks/pre-commit <<__PRE_COMMIT__ #!/bin/bash export ROSE_CONF_PATH=$PWD/conf -exec $ROSE_HOME/sbin/rosa svn-pre-commit "\$@" +export PATH=$PATH:${ROSE_BIN} +export ROSE_LIB=${ROSE_LIB} +rosa svn-pre-commit "\$@" __PRE_COMMIT__ chmod +x repos/foo/hooks/pre-commit export LANG=C diff --git a/t/rosa-svn-pre-commit/01-rosie-create.t b/t/rosa-svn-pre-commit/01-rosie-create.t index 6e8f806e12..50e9076927 100755 --- a/t/rosa-svn-pre-commit/01-rosie-create.t +++ b/t/rosa-svn-pre-commit/01-rosie-create.t @@ -20,6 +20,7 @@ # Tests for "rosa svn-pre-commit" with "rosie create". #------------------------------------------------------------------------------- . $(dirname $0)/test_header + #------------------------------------------------------------------------------- tests 18 #------------------------------------------------------------------------------- @@ -34,11 +35,15 @@ prefix-default=foo prefix-owner-default.foo=fred prefix-location.foo=$SVN_URL __ROSE_CONF__ +ROSE_BIN_HOME=$(dirname $(command -v rose)) +ROSE_LIB=$(dirname $(python -c "import metomi.rose; print(metomi.rose.__file__)")) export ROSE_CONF_PATH=$PWD/conf cat >repos/foo/hooks/pre-commit <<__PRE_COMMIT__ #!/bin/bash export ROSE_CONF_PATH=$ROSE_CONF_PATH -exec $ROSE_HOME/sbin/rosa svn-pre-commit --debug "\$@" +export PATH=$PATH:${ROSE_BIN_HOME} +export ROSE_LIB="${ROSE_LIB}" +rosa svn-pre-commit --debug "\$@" __PRE_COMMIT__ chmod +x repos/foo/hooks/pre-commit export LANG=C diff --git a/t/rosa-svn-pre-commit/02-passwd.t b/t/rosa-svn-pre-commit/02-passwd.t index cb89f1c033..3f77fbcb07 100755 --- a/t/rosa-svn-pre-commit/02-passwd.t +++ b/t/rosa-svn-pre-commit/02-passwd.t @@ -20,6 +20,7 @@ # Tests for "rosa svn-pre-commit", Unix passwd user check. #------------------------------------------------------------------------------- . $(dirname $0)/test_header + export ROSE_CONF_PATH= mkdir conf cat >conf/rose.conf <<'__ROSE_CONF__' @@ -33,10 +34,14 @@ tests 13 mkdir repos svnadmin create repos/foo SVN_URL=file://$PWD/repos/foo +ROSE_BIN_HOME=$(dirname $(command -v rose)) +ROSE_LIB=$(dirname $(python -c "import metomi.rose; print(metomi.rose.__file__)")) cat >repos/foo/hooks/pre-commit <<__PRE_COMMIT__ #!/bin/bash export ROSE_CONF_PATH=$PWD/conf -exec $ROSE_HOME/sbin/rosa svn-pre-commit "\$@" +export PATH=$PATH:${ROSE_BIN_HOME} +export ROSE_LIB="${ROSE_LIB}" +rosa svn-pre-commit "\$@" __PRE_COMMIT__ chmod +x repos/foo/hooks/pre-commit export LANG=C diff --git a/t/rosa-svn-pre-commit/03-meta.t b/t/rosa-svn-pre-commit/03-meta.t index 03ca906241..f064be8a93 100755 --- a/t/rosa-svn-pre-commit/03-meta.t +++ b/t/rosa-svn-pre-commit/03-meta.t @@ -20,6 +20,7 @@ # Tests for "rosa svn-pre-commit", validate against configuration metadata. #------------------------------------------------------------------------------- . "$(dirname "$0")/test_header" + export ROSE_CONF_PATH= mkdir -p 'conf' 'rose-meta/foolish/HEAD' cat >'conf/rose.conf' <<__ROSE_CONF__ @@ -39,10 +40,14 @@ tests 10 mkdir 'repos' svnadmin create 'repos/foo' SVN_URL="file://${PWD}/repos/foo" +ROSE_BIN_HOME=$(dirname $(command -v rose)) +ROSE_LIB=$(dirname $(python -c "import metomi.rose; print(metomi.rose.__file__)")) cat >'repos/foo/hooks/pre-commit' <<__PRE_COMMIT__ #!/bin/bash export ROSE_CONF_PATH=${PWD}/conf -exec ${ROSE_HOME}/sbin/rosa svn-pre-commit "\$@" +export PATH=$PATH:${ROSE_BIN_HOME} +export ROSE_LIB="${ROSE_LIB}" +rosa svn-pre-commit "\$@" __PRE_COMMIT__ chmod +x 'repos/foo/hooks/pre-commit' export LANG='C' diff --git a/t/rose-app-run/17-file-db-bad.t b/t/rose-app-run/17-file-db-bad.t index 5ee43e3c0f..2d795a193a 100755 --- a/t/rose-app-run/17-file-db-bad.t +++ b/t/rose-app-run/17-file-db-bad.t @@ -28,13 +28,14 @@ test_init <<'__CONFIG__' default=true [file:COPYING] -source=$ROSE_HOME/COPYING +source=$ROSE_TEST_HOME/COPYING __CONFIG__ #------------------------------------------------------------------------------- TEST_KEY=$TEST_KEY_BASE test_setup touch .rose-config_processors-file.db -run_pass "$TEST_KEY" rose app-run --config=../config -q +run_pass "$TEST_KEY" rose app-run --config=../config -q --debug +cat $TEST_KEY.err >&2 file_test "$TEST_KEY.db" .rose-config_processors-file.db -s file_test "$TEST_KEY.COPYING" COPYING test_teardown diff --git a/t/rose-app-upgrade/00-null.t b/t/rose-app-upgrade/00-null.t index 7a925364f5..d0fc564c5c 100755 --- a/t/rose-app-upgrade/00-null.t +++ b/t/rose-app-upgrade/00-null.t @@ -142,10 +142,10 @@ init_macro test-app-upgrade <<'__MACRO__' # -*- coding: utf-8 -*- -import rose.upgrade +import metomi.rose.upgrade -class Upgrade02to03(rose.upgrade.MacroUpgrade): +class Upgrade02to03(metomi.rose.upgrade.MacroUpgrade): """Upgrade from 0.2 to 0.3.""" @@ -178,10 +178,10 @@ init_macro test-app-upgrade << '__MACRO__' # -*- coding: utf-8 -*- -import rose.upgrade +import metomi.rose.upgrade -class Upgrade02to03(rose.upgrade.MacroUpgrade): +class Upgrade02to03(metomi.rose.upgrade.MacroUpgrade): """Upgrade from 0.2 to 0.3.""" @@ -214,10 +214,10 @@ init_macro test-app-upgrade <<'__MACRO__' # -*- coding: utf-8 -*- -import rose.upgrade +import metomi.rose.upgrade -class Upgrade02to03(rose.upgrade.MacroUpgrade): +class Upgrade02to03(metomi.rose.upgrade.MacroUpgrade): """Upgrade from 0.2 to 0.3.""" @@ -250,7 +250,7 @@ init_macro test-app-upgrade <<'__MACRO__' # -*- coding: utf-8 -*- -import rose.upgrade +import metomi.rose.upgrade __MACRO__ run_fail "$TEST_KEY" rose app-upgrade --non-interactive \ --meta-path=../rose-meta/ -C ../config 0.1 @@ -277,10 +277,10 @@ init_macro test-app-upgrade <<'__MACRO__' # -*- coding: utf-8 -*- -import rose.upgrade +import metomi.rose.upgrade -class Upgrade01to02(rose.upgrade.MacroUpgrade): +class Upgrade01to02(metomi.rose.upgrade.MacroUpgrade): """Upgrade from 0.1 to 0.2.""" @@ -291,7 +291,7 @@ class Upgrade01to02(rose.upgrade.MacroUpgrade): return config, self.reports -class Upgrade02to03(rose.upgrade.MacroUpgrade): +class Upgrade02to03(metomi.rose.upgrade.MacroUpgrade): """Upgrade from 0.2 to 0.3.""" diff --git a/t/rose-app-upgrade/01-upgrade-basic.t b/t/rose-app-upgrade/01-upgrade-basic.t index f358b2f11d..3f006f4b2c 100755 --- a/t/rose-app-upgrade/01-upgrade-basic.t +++ b/t/rose-app-upgrade/01-upgrade-basic.t @@ -38,10 +38,10 @@ init_macro test-app-upgrade <<'__MACRO__' # -*- coding: utf-8 -*- -import rose.upgrade +import metomi.rose.upgrade -class Upgrade01to02(rose.upgrade.MacroUpgrade): +class Upgrade01to02(metomi.rose.upgrade.MacroUpgrade): """Upgrade from 0.1 to 0.2.""" @@ -54,7 +54,7 @@ class Upgrade01to02(rose.upgrade.MacroUpgrade): return config, self.reports -class Upgrade02to03(rose.upgrade.MacroUpgrade): +class Upgrade02to03(metomi.rose.upgrade.MacroUpgrade): """Upgrade from 0.2 to 0.3.""" @@ -66,7 +66,7 @@ class Upgrade02to03(rose.upgrade.MacroUpgrade): return config, self.reports -class Upgrade03to04(rose.upgrade.MacroUpgrade): +class Upgrade03to04(metomi.rose.upgrade.MacroUpgrade): """Upgrade from 0.3 to 0.4.""" @@ -78,7 +78,7 @@ class Upgrade03to04(rose.upgrade.MacroUpgrade): return config, self.reports -class Upgrade04to05(rose.upgrade.MacroUpgrade): +class Upgrade04to05(metomi.rose.upgrade.MacroUpgrade): """Upgrade from 0.4 to 0.5.""" @@ -90,7 +90,7 @@ class Upgrade04to05(rose.upgrade.MacroUpgrade): return config, self.reports -class Upgrade05to051(rose.upgrade.MacroUpgrade): +class Upgrade05to051(metomi.rose.upgrade.MacroUpgrade): """Upgrade from 0.5 to 0.5.1.""" @@ -102,7 +102,7 @@ class Upgrade05to051(rose.upgrade.MacroUpgrade): return config, self.reports -class Upgrade051to10(rose.upgrade.MacroUpgrade): +class Upgrade051to10(metomi.rose.upgrade.MacroUpgrade): """Upgrade from 0.5.1 to 1.0.""" @@ -474,10 +474,10 @@ init_macro test-app-upgrade <<'__MACRO__' # -*- coding: utf-8 -*- -import rose.upgrade +import metomi.rose.upgrade -class Upgrade01to02(rose.upgrade.MacroUpgrade): +class Upgrade01to02(metomi.rose.upgrade.MacroUpgrade): """Upgrade from 0.1 to 0.2.""" @@ -488,7 +488,7 @@ class Upgrade01to02(rose.upgrade.MacroUpgrade): return config, self.reports -class Upgrade02to03(rose.upgrade.MacroUpgrade): +class Upgrade02to03(metomi.rose.upgrade.MacroUpgrade): """Upgrade from 0.2 to 0.3.""" @@ -499,7 +499,7 @@ class Upgrade02to03(rose.upgrade.MacroUpgrade): return config, self.reports -class Upgrade04to05(rose.upgrade.MacroUpgrade): +class Upgrade04to05(metomi.rose.upgrade.MacroUpgrade): """Upgrade from 0.4 to 0.5.""" @@ -547,10 +547,10 @@ init_macro test-app-upgrade <<'__MACRO__' # -*- coding: utf-8 -*- -import rose.upgrade +import metomi.rose.upgrade -class Upgrade01to02(rose.upgrade.MacroUpgrade): +class Upgrade01to02(metomi.rose.upgrade.MacroUpgrade): """Upgrade from 0.1 to 0.2.""" @@ -563,7 +563,7 @@ class Upgrade01to02(rose.upgrade.MacroUpgrade): return config, self.reports -class Upgrade02to03(rose.upgrade.MacroUpgrade): +class Upgrade02to03(metomi.rose.upgrade.MacroUpgrade): """Upgrade from 0.2 to 0.3.""" @@ -575,7 +575,7 @@ class Upgrade02to03(rose.upgrade.MacroUpgrade): return config, self.reports -class Upgrade04to05(rose.upgrade.MacroUpgrade): +class Upgrade04to05(metomi.rose.upgrade.MacroUpgrade): """Upgrade from 0.4 to 0.5.""" @@ -625,10 +625,10 @@ init_macro test-app-upgrade <<'__MACRO__' # -*- coding: utf-8 -*- -import rose.upgrade +import metomi.rose.upgrade -class Upgrade01to02(rose.upgrade.MacroUpgrade): +class Upgrade01to02(metomi.rose.upgrade.MacroUpgrade): """Upgrade from 0.1 to 0.2.""" @@ -641,7 +641,7 @@ class Upgrade01to02(rose.upgrade.MacroUpgrade): return config, self.reports -class Upgrade02to03(rose.upgrade.MacroUpgrade): +class Upgrade02to03(metomi.rose.upgrade.MacroUpgrade): """Upgrade from 0.2 to 0.3.""" @@ -653,7 +653,7 @@ class Upgrade02to03(rose.upgrade.MacroUpgrade): return config, self.reports -class Upgrade04to05(rose.upgrade.MacroUpgrade): +class Upgrade04to05(metomi.rose.upgrade.MacroUpgrade): """Upgrade from 0.4 to 0.5.""" @@ -705,10 +705,10 @@ init_macro test-app-upgrade <<'__MACRO__' # -*- coding: utf-8 -*- -import rose.upgrade +import metomi.rose.upgrade -class Upgrade01to02(rose.upgrade.MacroUpgrade): +class Upgrade01to02(metomi.rose.upgrade.MacroUpgrade): """Upgrade from 0.1 to 0.2.""" diff --git a/t/rose-app-upgrade/02-downgrade-basic.t b/t/rose-app-upgrade/02-downgrade-basic.t index 68d38ec364..d26dc74e58 100755 --- a/t/rose-app-upgrade/02-downgrade-basic.t +++ b/t/rose-app-upgrade/02-downgrade-basic.t @@ -38,10 +38,10 @@ init_macro test-app-upgrade <<'__MACRO__' # -*- coding: utf-8 -*- -import rose.upgrade +import metomi.rose.upgrade -class Upgrade01to02(rose.upgrade.MacroUpgrade): +class Upgrade01to02(metomi.rose.upgrade.MacroUpgrade): """Upgrade from 0.1 to 0.2.""" @@ -59,7 +59,7 @@ class Upgrade01to02(rose.upgrade.MacroUpgrade): return config, self.reports -class Upgrade02to03(rose.upgrade.MacroUpgrade): +class Upgrade02to03(metomi.rose.upgrade.MacroUpgrade): """Upgrade from 0.2 to 0.3.""" @@ -75,7 +75,7 @@ class Upgrade02to03(rose.upgrade.MacroUpgrade): return config, self.reports -class Upgrade03to04(rose.upgrade.MacroUpgrade): +class Upgrade03to04(metomi.rose.upgrade.MacroUpgrade): """Upgrade from 0.3 to 0.4.""" @@ -91,7 +91,7 @@ class Upgrade03to04(rose.upgrade.MacroUpgrade): return config, self.reports -class Upgrade04to041(rose.upgrade.MacroUpgrade): +class Upgrade04to041(metomi.rose.upgrade.MacroUpgrade): """Upgrade from 0.4 to 0.4.1.""" @@ -107,7 +107,7 @@ class Upgrade04to041(rose.upgrade.MacroUpgrade): return config, self.reports -class Upgrade041to10(rose.upgrade.MacroUpgrade): +class Upgrade041to10(metomi.rose.upgrade.MacroUpgrade): """Upgrade from 0.4.1 to 1.0.""" @@ -392,10 +392,10 @@ init_macro test-app-upgrade <<'__MACRO__' # -*- coding: utf-8 -*- -import rose.upgrade +import metomi.rose.upgrade -class Upgrade01to02(rose.upgrade.MacroUpgrade): +class Upgrade01to02(metomi.rose.upgrade.MacroUpgrade): """Upgrade from 0.1 to 0.2.""" @@ -406,7 +406,7 @@ class Upgrade01to02(rose.upgrade.MacroUpgrade): return config, self.reports -class Upgrade02to03(rose.upgrade.MacroUpgrade): +class Upgrade02to03(metomi.rose.upgrade.MacroUpgrade): """Upgrade from 0.2 to 0.3.""" @@ -417,7 +417,7 @@ class Upgrade02to03(rose.upgrade.MacroUpgrade): return config, self.reports -class Upgrade04to05(rose.upgrade.MacroUpgrade): +class Upgrade04to05(metomi.rose.upgrade.MacroUpgrade): """Upgrade from 0.4 to 0.5.""" @@ -453,10 +453,10 @@ init_macro test-app-upgrade <<'__MACRO__' # -*- coding: utf-8 -*- -import rose.upgrade +import metomi.rose.upgrade -class Upgrade01to02(rose.upgrade.MacroUpgrade): +class Upgrade01to02(metomi.rose.upgrade.MacroUpgrade): """Upgrade from 0.1 to 0.2.""" @@ -474,7 +474,7 @@ class Upgrade01to02(rose.upgrade.MacroUpgrade): return config, self.reports -class Upgrade02to03(rose.upgrade.MacroUpgrade): +class Upgrade02to03(metomi.rose.upgrade.MacroUpgrade): """Upgrade from 0.2 to 0.3.""" @@ -490,7 +490,7 @@ class Upgrade02to03(rose.upgrade.MacroUpgrade): return config, self.reports -class Upgrade04to05(rose.upgrade.MacroUpgrade): +class Upgrade04to05(metomi.rose.upgrade.MacroUpgrade): """Upgrade from 0.4 to 0.5.""" @@ -573,10 +573,10 @@ init_macro test-app-upgrade <<'__MACRO__' # -*- coding: utf-8 -*- -import rose.upgrade +import metomi.rose.upgrade -class Upgrade01to02(rose.upgrade.MacroUpgrade): +class Upgrade01to02(metomi.rose.upgrade.MacroUpgrade): """Upgrade from 0.1 to 0.2.""" diff --git a/t/rose-app-upgrade/03-complex.t b/t/rose-app-upgrade/03-complex.t index b1149d1846..73badcebc7 100755 --- a/t/rose-app-upgrade/03-complex.t +++ b/t/rose-app-upgrade/03-complex.t @@ -158,10 +158,10 @@ init_macro test-app-upgrade <<'__MACRO__' # -*- coding: utf-8 -*- -import rose.upgrade +import metomi.rose.upgrade -class UpgradeAppletoFig(rose.upgrade.MacroUpgrade): +class UpgradeAppletoFig(metomi.rose.upgrade.MacroUpgrade): """Upgrade from Apple to Fig.""" @@ -670,10 +670,10 @@ init_macro test-app-upgrade <<'__MACRO__' # -*- coding: utf-8 -*- -import rose.upgrade +import metomi.rose.upgrade -class UpgradeAppletoFig(rose.upgrade.MacroUpgrade): +class UpgradeAppletoFig(metomi.rose.upgrade.MacroUpgrade): """Upgrade from Apple to Fig.""" diff --git a/t/rose-app-upgrade/04-upgrade-trigger.t b/t/rose-app-upgrade/04-upgrade-trigger.t index 5539023490..f407b60c70 100755 --- a/t/rose-app-upgrade/04-upgrade-trigger.t +++ b/t/rose-app-upgrade/04-upgrade-trigger.t @@ -44,10 +44,10 @@ init_macro test-app-upgrade <<'__MACRO__' # -*- coding: utf-8 -*- -import rose.upgrade +import metomi.rose.upgrade -class Upgrade01to02(rose.upgrade.MacroUpgrade): +class Upgrade01to02(metomi.rose.upgrade.MacroUpgrade): """Upgrade from 0.1 to 0.2.""" diff --git a/t/rose-app-upgrade/06-broken.t b/t/rose-app-upgrade/06-broken.t index 2b9b109158..1b3656779f 100755 --- a/t/rose-app-upgrade/06-broken.t +++ b/t/rose-app-upgrade/06-broken.t @@ -36,10 +36,10 @@ init_macro test-app-upgrade <<'__MACRO__' # -*- coding: utf-8 -*- -import rose.upgrade +import metomi.rose.upgrade -class UpgradeWhatevertoOtherWhatever(rose.upgrade.MacroUpgrade): +class UpgradeWhatevertoOtherWhatever(metomi.rose.upgrade.MacroUpgrade): """Upgrade from Whatever to Other Whatever.""" @@ -50,7 +50,7 @@ class UpgradeWhatevertoOtherWhatever(rose.upgrade.MacroUpgrade): return config, self.reports -class UpgradeDunnoToOtherDunno(rose.upgrade.MacroUpgrade): +class UpgradeDunnoToOtherDunno(metomi.rose.upgrade.MacroUpgrade): """Upgrade from Dunno to Other Dunno.""" @@ -102,7 +102,7 @@ rm "../rose-meta/test-app-upgrade/versions.pyc" init_macro test-app-upgrade <<'__MACRO__' #!/usr/bin/env python3 -import rose.upgrade +import metomi.rose.upgrade import some_broken_import __MACRO__ diff --git a/t/rose-app-upgrade/07-add-existing.t b/t/rose-app-upgrade/07-add-existing.t index dbec0b8830..13a53e3d0a 100755 --- a/t/rose-app-upgrade/07-add-existing.t +++ b/t/rose-app-upgrade/07-add-existing.t @@ -40,10 +40,10 @@ init_macro park <<'__MACRO__' # -*- coding: utf-8 -*- -import rose.upgrade +import metomi.rose.upgrade -class UpgradeAddDinosaurs(rose.upgrade.MacroUpgrade): +class UpgradeAddDinosaurs(metomi.rose.upgrade.MacroUpgrade): """Install dinosaurs in our super-secure facility.""" diff --git a/t/rose-app-upgrade/08-category-is-package.t b/t/rose-app-upgrade/08-category-is-package.t index bc8f3bcefd..c14822b399 100755 --- a/t/rose-app-upgrade/08-category-is-package.t +++ b/t/rose-app-upgrade/08-category-is-package.t @@ -41,10 +41,10 @@ cat >$TEST_DIR/rose-meta/$category/sith.py <<'__MACRO__' # -*- coding: utf-8 -*- -import rose.upgrade +import metomi.rose.upgrade -class UpgradeAddLightSaber(rose.upgrade.MacroUpgrade): +class UpgradeAddLightSaber(metomi.rose.upgrade.MacroUpgrade): """'An elegant weapon, for a more civilized age.' diff --git a/t/rose-app-upgrade/09-opt.t b/t/rose-app-upgrade/09-opt.t index 7806c7b0d1..cccd2bb671 100755 --- a/t/rose-app-upgrade/09-opt.t +++ b/t/rose-app-upgrade/09-opt.t @@ -42,10 +42,10 @@ init_macro test-app-upgrade <<'__MACRO__' # -*- coding: utf-8 -*- -import rose.upgrade +import metomi.rose.upgrade -class Upgrade01to02(rose.upgrade.MacroUpgrade): +class Upgrade01to02(metomi.rose.upgrade.MacroUpgrade): """Upgrade from 0.1 to 0.2.""" diff --git a/t/rose-app-upgrade/10-prettify-trigger-bug.t b/t/rose-app-upgrade/10-prettify-trigger-bug.t index 955a9cd814..eeb9e51699 100755 --- a/t/rose-app-upgrade/10-prettify-trigger-bug.t +++ b/t/rose-app-upgrade/10-prettify-trigger-bug.t @@ -54,9 +54,9 @@ init_macro tulip <<'__MACRO__' #!/usr/bin/env python3 # -*- coding: utf-8 -*- -import rose.upgrade +import metomi.rose.upgrade -class UpgradeTulipToBlue(rose.upgrade.MacroUpgrade): +class UpgradeTulipToBlue(metomi.rose.upgrade.MacroUpgrade): """Blue tulips are prettier (?).""" diff --git a/t/rose-app-upgrade/11-upgrade-basic-tree.t b/t/rose-app-upgrade/11-upgrade-basic-tree.t index 345ae8f238..8632f9f76b 100755 --- a/t/rose-app-upgrade/11-upgrade-basic-tree.t +++ b/t/rose-app-upgrade/11-upgrade-basic-tree.t @@ -38,10 +38,10 @@ init_macro test_tree/test-app-upgrade <<'__MACRO__' # -*- coding: utf-8 -*- -import rose.upgrade +import metomi.rose.upgrade -class Upgrade01to02(rose.upgrade.MacroUpgrade): +class Upgrade01to02(metomi.rose.upgrade.MacroUpgrade): """Upgrade from 0.1 to 0.2.""" @@ -54,7 +54,7 @@ class Upgrade01to02(rose.upgrade.MacroUpgrade): return config, self.reports -class Upgrade02to03(rose.upgrade.MacroUpgrade): +class Upgrade02to03(metomi.rose.upgrade.MacroUpgrade): """Upgrade from 0.2 to 0.3.""" @@ -66,7 +66,7 @@ class Upgrade02to03(rose.upgrade.MacroUpgrade): return config, self.reports -class Upgrade03to04(rose.upgrade.MacroUpgrade): +class Upgrade03to04(metomi.rose.upgrade.MacroUpgrade): """Upgrade from 0.3 to 0.4.""" @@ -78,7 +78,7 @@ class Upgrade03to04(rose.upgrade.MacroUpgrade): return config, self.reports -class Upgrade04to05(rose.upgrade.MacroUpgrade): +class Upgrade04to05(metomi.rose.upgrade.MacroUpgrade): """Upgrade from 0.4 to 0.5.""" @@ -90,7 +90,7 @@ class Upgrade04to05(rose.upgrade.MacroUpgrade): return config, self.reports -class Upgrade05to051(rose.upgrade.MacroUpgrade): +class Upgrade05to051(metomi.rose.upgrade.MacroUpgrade): """Upgrade from 0.5 to 0.5.1.""" @@ -102,7 +102,7 @@ class Upgrade05to051(rose.upgrade.MacroUpgrade): return config, self.reports -class Upgrade051to10(rose.upgrade.MacroUpgrade): +class Upgrade051to10(metomi.rose.upgrade.MacroUpgrade): """Upgrade from 0.5.1 to 1.0.""" @@ -474,10 +474,10 @@ init_macro test_tree/test-app-upgrade <<'__MACRO__' # -*- coding: utf-8 -*- -import rose.upgrade +import metomi.rose.upgrade -class Upgrade01to02(rose.upgrade.MacroUpgrade): +class Upgrade01to02(metomi.rose.upgrade.MacroUpgrade): """Upgrade from 0.1 to 0.2.""" @@ -488,7 +488,7 @@ class Upgrade01to02(rose.upgrade.MacroUpgrade): return config, self.reports -class Upgrade02to03(rose.upgrade.MacroUpgrade): +class Upgrade02to03(metomi.rose.upgrade.MacroUpgrade): """Upgrade from 0.2 to 0.3.""" @@ -499,7 +499,7 @@ class Upgrade02to03(rose.upgrade.MacroUpgrade): return config, self.reports -class Upgrade04to05(rose.upgrade.MacroUpgrade): +class Upgrade04to05(metomi.rose.upgrade.MacroUpgrade): """Upgrade from 0.4 to 0.5.""" @@ -547,10 +547,10 @@ init_macro test_tree/test-app-upgrade <<'__MACRO__' # -*- coding: utf-8 -*- -import rose.upgrade +import metomi.rose.upgrade -class Upgrade01to02(rose.upgrade.MacroUpgrade): +class Upgrade01to02(metomi.rose.upgrade.MacroUpgrade): """Upgrade from 0.1 to 0.2.""" @@ -563,7 +563,7 @@ class Upgrade01to02(rose.upgrade.MacroUpgrade): return config, self.reports -class Upgrade02to03(rose.upgrade.MacroUpgrade): +class Upgrade02to03(metomi.rose.upgrade.MacroUpgrade): """Upgrade from 0.2 to 0.3.""" @@ -575,7 +575,7 @@ class Upgrade02to03(rose.upgrade.MacroUpgrade): return config, self.reports -class Upgrade04to05(rose.upgrade.MacroUpgrade): +class Upgrade04to05(metomi.rose.upgrade.MacroUpgrade): """Upgrade from 0.4 to 0.5.""" @@ -625,10 +625,10 @@ init_macro test_tree/test-app-upgrade <<'__MACRO__' # -*- coding: utf-8 -*- -import rose.upgrade +import metomi.rose.upgrade -class Upgrade01to02(rose.upgrade.MacroUpgrade): +class Upgrade01to02(metomi.rose.upgrade.MacroUpgrade): """Upgrade from 0.1 to 0.2.""" @@ -641,7 +641,7 @@ class Upgrade01to02(rose.upgrade.MacroUpgrade): return config, self.reports -class Upgrade02to03(rose.upgrade.MacroUpgrade): +class Upgrade02to03(metomi.rose.upgrade.MacroUpgrade): """Upgrade from 0.2 to 0.3.""" @@ -653,7 +653,7 @@ class Upgrade02to03(rose.upgrade.MacroUpgrade): return config, self.reports -class Upgrade04to05(rose.upgrade.MacroUpgrade): +class Upgrade04to05(metomi.rose.upgrade.MacroUpgrade): """Upgrade from 0.4 to 0.5.""" @@ -705,10 +705,10 @@ init_macro test_tree/test-app-upgrade <<'__MACRO__' # -*- coding: utf-8 -*- -import rose.upgrade +import metomi.rose.upgrade -class Upgrade01to02(rose.upgrade.MacroUpgrade): +class Upgrade01to02(metomi.rose.upgrade.MacroUpgrade): """Upgrade from 0.1 to 0.2.""" diff --git a/t/rose-app-upgrade/12-downgrade-basic-tree.t b/t/rose-app-upgrade/12-downgrade-basic-tree.t index 3477c8dc2c..d47cc1874d 100755 --- a/t/rose-app-upgrade/12-downgrade-basic-tree.t +++ b/t/rose-app-upgrade/12-downgrade-basic-tree.t @@ -38,10 +38,10 @@ init_macro test_tree/test-app-upgrade <<'__MACRO__' # -*- coding: utf-8 -*- -import rose.upgrade +import metomi.rose.upgrade -class Upgrade01to02(rose.upgrade.MacroUpgrade): +class Upgrade01to02(metomi.rose.upgrade.MacroUpgrade): """Upgrade from 0.1 to 0.2.""" @@ -59,7 +59,7 @@ class Upgrade01to02(rose.upgrade.MacroUpgrade): return config, self.reports -class Upgrade02to03(rose.upgrade.MacroUpgrade): +class Upgrade02to03(metomi.rose.upgrade.MacroUpgrade): """Upgrade from 0.2 to 0.3.""" @@ -75,7 +75,7 @@ class Upgrade02to03(rose.upgrade.MacroUpgrade): return config, self.reports -class Upgrade03to04(rose.upgrade.MacroUpgrade): +class Upgrade03to04(metomi.rose.upgrade.MacroUpgrade): """Upgrade from 0.3 to 0.4.""" @@ -91,7 +91,7 @@ class Upgrade03to04(rose.upgrade.MacroUpgrade): return config, self.reports -class Upgrade04to041(rose.upgrade.MacroUpgrade): +class Upgrade04to041(metomi.rose.upgrade.MacroUpgrade): """Upgrade from 0.4 to 0.4.1.""" @@ -107,7 +107,7 @@ class Upgrade04to041(rose.upgrade.MacroUpgrade): return config, self.reports -class Upgrade041to10(rose.upgrade.MacroUpgrade): +class Upgrade041to10(metomi.rose.upgrade.MacroUpgrade): """Upgrade from 0.4.1 to 1.0.""" @@ -392,10 +392,10 @@ init_macro test_tree/test-app-upgrade <<'__MACRO__' # -*- coding: utf-8 -*- -import rose.upgrade +import metomi.rose.upgrade -class Upgrade01to02(rose.upgrade.MacroUpgrade): +class Upgrade01to02(metomi.rose.upgrade.MacroUpgrade): """Upgrade from 0.1 to 0.2.""" @@ -406,7 +406,7 @@ class Upgrade01to02(rose.upgrade.MacroUpgrade): return config, self.reports -class Upgrade02to03(rose.upgrade.MacroUpgrade): +class Upgrade02to03(metomi.rose.upgrade.MacroUpgrade): """Upgrade from 0.2 to 0.3.""" @@ -417,7 +417,7 @@ class Upgrade02to03(rose.upgrade.MacroUpgrade): return config, self.reports -class Upgrade04to05(rose.upgrade.MacroUpgrade): +class Upgrade04to05(metomi.rose.upgrade.MacroUpgrade): """Upgrade from 0.4 to 0.5.""" @@ -453,10 +453,10 @@ init_macro test_tree/test-app-upgrade <<'__MACRO__' # -*- coding: utf-8 -*- -import rose.upgrade +import metomi.rose.upgrade -class Upgrade01to02(rose.upgrade.MacroUpgrade): +class Upgrade01to02(metomi.rose.upgrade.MacroUpgrade): """Upgrade from 0.1 to 0.2.""" @@ -474,7 +474,7 @@ class Upgrade01to02(rose.upgrade.MacroUpgrade): return config, self.reports -class Upgrade02to03(rose.upgrade.MacroUpgrade): +class Upgrade02to03(metomi.rose.upgrade.MacroUpgrade): """Upgrade from 0.2 to 0.3.""" @@ -490,7 +490,7 @@ class Upgrade02to03(rose.upgrade.MacroUpgrade): return config, self.reports -class Upgrade04to05(rose.upgrade.MacroUpgrade): +class Upgrade04to05(metomi.rose.upgrade.MacroUpgrade): """Upgrade from 0.4 to 0.5.""" @@ -573,10 +573,10 @@ init_macro test_tree/test-app-upgrade <<'__MACRO__' # -*- coding: utf-8 -*- -import rose.upgrade +import metomi.rose.upgrade -class Upgrade01to02(rose.upgrade.MacroUpgrade): +class Upgrade01to02(metomi.rose.upgrade.MacroUpgrade): """Upgrade from 0.1 to 0.2.""" diff --git a/t/rose-app-upgrade/13-bad-upgrade-macro.t b/t/rose-app-upgrade/13-bad-upgrade-macro.t index 5f7b503e6a..de23d0b635 100755 --- a/t/rose-app-upgrade/13-bad-upgrade-macro.t +++ b/t/rose-app-upgrade/13-bad-upgrade-macro.t @@ -39,10 +39,10 @@ init_macro test-app-upgrade <<'__MACRO__' #!/usr/bin/env python3 # -*- coding: utf-8 -*- -import rose.upgrade +import metomi.rose.upgrade -class Upgrade01to02(rose.upgrade.MacroUpgrade): +class Upgrade01to02(metomi.rose.upgrade.MacroUpgrade): """Test class to upgrade the macro with a bad change and fail on save.""" diff --git a/t/rose-app-upgrade/lib/versions_cwd.py b/t/rose-app-upgrade/lib/versions_cwd.py index f385017872..1d87ff94f7 100644 --- a/t/rose-app-upgrade/lib/versions_cwd.py +++ b/t/rose-app-upgrade/lib/versions_cwd.py @@ -4,10 +4,10 @@ import os -import rose.upgrade +import metomi.rose.upgrade -class UpgradeAppletoFig(rose.upgrade.MacroUpgrade): +class UpgradeAppletoFig(metomi.rose.upgrade.MacroUpgrade): """Upgrade from Apple to Fig.""" diff --git a/t/rose-cli-bash-completion/00-static.t b/t/rose-cli-bash-completion/00-static.t index 4800d23be0..5ee8f9a1e5 100755 --- a/t/rose-cli-bash-completion/00-static.t +++ b/t/rose-cli-bash-completion/00-static.t @@ -21,11 +21,11 @@ #------------------------------------------------------------------------------- . $(dirname $0)/test_header #------------------------------------------------------------------------------- -tests 197 +tests 193 setup #------------------------------------------------------------------------------- # Source the script. -. $ROSE_HOME/etc/rose-bash-completion || exit 1 +. $ROSE_TEST_HOME/etc/rose-bash-completion || exit 1 #------------------------------------------------------------------------------- # List Rose subcommands. TEST_KEY=$TEST_KEY_BASE-rose-subcommands @@ -277,10 +277,10 @@ init_upgrade_macro jupiter_moons <<'__MACRO__' # -*- coding: utf-8 -*- -import rose.upgrade +import metomi.rose.upgrade -class UpgradeIotoEuropa(rose.upgrade.MacroUpgrade): +class UpgradeIotoEuropa(metomi.rose.upgrade.MacroUpgrade): """Upgrade from Io to Europa.""" @@ -292,7 +292,7 @@ class UpgradeIotoEuropa(rose.upgrade.MacroUpgrade): return config, self.reports -class UpgradeEuropatoGanymede(rose.upgrade.MacroUpgrade): +class UpgradeEuropatoGanymede(metomi.rose.upgrade.MacroUpgrade): """Upgrade from Europa to Ganymede.""" @@ -304,7 +304,7 @@ class UpgradeEuropatoGanymede(rose.upgrade.MacroUpgrade): return config, self.reports -class UpgradeGanymedetoCallisto(rose.upgrade.MacroUpgrade): +class UpgradeGanymedetoCallisto(metomi.rose.upgrade.MacroUpgrade): """Upgrade from Ganymede to Callisto.""" @@ -316,7 +316,7 @@ class UpgradeGanymedetoCallisto(rose.upgrade.MacroUpgrade): return config, self.reports -class UpgradeCallistotoThemisto(rose.upgrade.MacroUpgrade): +class UpgradeCallistotoThemisto(metomi.rose.upgrade.MacroUpgrade): """Upgrade from Callisto to Themisto.""" @@ -384,8 +384,8 @@ COMP_WORDS=( rose macro -C ../config "" ) COMP_CWORD=4 COMPREPLY= run_pass "$TEST_KEY" _rose -compreply_grep "$TEST_KEY.reply1" '^rose.macros.DefaultTransforms$' -compreply_grep "$TEST_KEY.reply2" '^rose.macros.DefaultValidators$' +compreply_grep "$TEST_KEY.reply1" '^metomi.rose.macros.DefaultTransforms$' +compreply_grep "$TEST_KEY.reply2" '^metomi.rose.macros.DefaultValidators$' compreply_grep "$TEST_KEY.reply3" '^--config=$' compreply_grep "$TEST_KEY.reply4" '^-C$' file_cmp "$TEST_KEY.out" "$TEST_KEY.out" . -#------------------------------------------------------------------------------- -# Test PYTHONPATH in "rose env-cat". -# This is really a test for the "rose" command itself. -# See #1244. -#------------------------------------------------------------------------------- -. $(dirname $0)/test_header - -tests 7 -#------------------------------------------------------------------------------- -TEST_KEY="$TEST_KEY_BASE-null" -PYTHONPATH= rose env-cat <<<'$PYTHONPATH' >"$TEST_KEY.out" -run_pass "$TEST_KEY.out" \ - grep -q "^${ROSE_HOME}/lib/python\(:[^:]*\)*$" "${TEST_KEY}.out" -#------------------------------------------------------------------------------- -TEST_KEY="$TEST_KEY_BASE-head-1" -PYTHONPATH="$ROSE_HOME/lib/python" \ - rose env-cat <<<'$PYTHONPATH' >"$TEST_KEY.out" -run_pass "$TEST_KEY.out" \ - grep -q "^${ROSE_HOME}/lib/python\(:[^:]*\)*$" "${TEST_KEY}.out" -#------------------------------------------------------------------------------- -mkdir -p {foo,bar}/lib/python -EXPECTED="$ROSE_HOME/lib/python:$PWD/foo/lib/python:$PWD/bar/lib/python" -PATTERN="^${ROSE_HOME}/lib/python\(:[^:]*\)*:${PWD}/foo/lib/python:${PWD}/bar/lib/python$" - -TEST_KEY="$TEST_KEY_BASE-head-2" -PYTHONPATH="$ROSE_HOME/lib/python:$PWD/foo/lib/python:$PWD/bar/lib/python" \ - rose env-cat <<<'$PYTHONPATH' >"$TEST_KEY.out" -run_pass "$TEST_KEY.out" grep -q "${PATTERN}" "${TEST_KEY}.out" - -TEST_KEY="$TEST_KEY_BASE-tail" -PYTHONPATH="$PWD/foo/lib/python:$PWD/bar/lib/python:$ROSE_HOME/lib/python" \ - rose env-cat <<<'$PYTHONPATH' >"$TEST_KEY.out" -run_pass "$TEST_KEY.out" grep -q "${PATTERN}" "${TEST_KEY}.out" - -TEST_KEY="$TEST_KEY_BASE-body-1" -PYTHONPATH="$PWD/foo/lib/python:$ROSE_HOME/lib/python:$PWD/bar/lib/python" \ - rose env-cat <<<'$PYTHONPATH' >"$TEST_KEY.out" -run_pass "$TEST_KEY.out" grep -q "${PATTERN}" "${TEST_KEY}.out" - -TEST_KEY="$TEST_KEY_BASE-body-2" -PYTHONPATH="$PWD/foo/lib/python:$ROSE_HOME/lib/python:$ROSE_HOME/lib/python:$PWD/bar/lib/python" \ - rose env-cat <<<'$PYTHONPATH' >"$TEST_KEY.out" -run_pass "$TEST_KEY.out" grep -q "${PATTERN}" "${TEST_KEY}.out" - -TEST_KEY="$TEST_KEY_BASE-body-tail" -PYTHONPATH="$PWD/foo/lib/python:$ROSE_HOME/lib/python:$PWD/bar/lib/python:$ROSE_HOME/lib/python" \ - rose env-cat <<<'$PYTHONPATH' >"$TEST_KEY.out" -run_pass "$TEST_KEY.out" grep -q "${PATTERN}" "${TEST_KEY}.out" -#------------------------------------------------------------------------------- -exit diff --git a/t/rose-macro/00-null.t b/t/rose-macro/00-null.t index a7cf83099c..ebbaa278ad 100755 --- a/t/rose-macro/00-null.t +++ b/t/rose-macro/00-null.t @@ -64,9 +64,9 @@ TEST_KEY=$TEST_KEY_BASE-no-metadata setup run_pass "$TEST_KEY" rose macro -C ../config file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<'__CONTENT__' -[V] rose.macros.DefaultValidators +[V] metomi.rose.macros.DefaultValidators # Runs all the default checks, such as compulsory checking. -[T] rose.macros.DefaultTransforms +[T] metomi.rose.macros.DefaultTransforms # Runs all the default fixers, such as trigger fixing. __CONTENT__ file_cmp "$TEST_KEY.err" "$TEST_KEY.err" this - 100 __META_CONFIG__ -run_pass "$TEST_KEY" rose macro --config=../config rose.macros.DefaultValidators +run_pass "$TEST_KEY" rose macro --config=../config metomi.rose.macros.DefaultValidators file_cmp "$TEST_KEY.out" "$TEST_KEY.out" 0 [namelist:values_nl1=my_real_sci_notation_pos] range = 0 < this / 100 < 6 __META_CONFIG__ -run_fail "$TEST_KEY" rose macro --config=../config rose.macros.DefaultValidators +run_fail "$TEST_KEY" rose macro --config=../config metomi.rose.macros.DefaultValidators file_cmp "$TEST_KEY.out" "$TEST_KEY.out" true,false,true namelist:values_nl1=my_char='Character string with no surrounding quotes' diff --git a/t/rose-macro/08-compulsory-check.t b/t/rose-macro/08-compulsory-check.t index eeb1ab5f37..3fffc99391 100755 --- a/t/rose-macro/08-compulsory-check.t +++ b/t/rose-macro/08-compulsory-check.t @@ -105,7 +105,7 @@ compulsory=true [namelist:compulsory_nl7=my_var9] compulsory=true __META_CONFIG__ -run_pass "$TEST_KEY" rose macro --config=../config rose.macros.DefaultValidators +run_pass "$TEST_KEY" rose macro --config=../config metomi.rose.macros.DefaultValidators file_cmp "$TEST_KEY.out" "$TEST_KEY.out" 0 simple:warn_test=test_var_3=-1 @@ -465,10 +465,10 @@ fail-if=oh no bad syntax, syntax:bad_before_id_expansion=bar [syntax:bad_after_id_expansion=foo] fail-if=this * 20 syntax:bad_after_id_expansion=bar __META_CONFIG__ -run_fail "$TEST_KEY" rose macro --config=../config rose.macros.DefaultValidators +run_fail "$TEST_KEY" rose macro --config=../config metomi.rose.macros.DefaultValidators file_cmp "$TEST_KEY.out" "$TEST_KEY.out" trig-ignored namelist:already_triggered_ignored_namelist=ab_trig_var2=2 @@ -972,7 +972,7 @@ duplicate=true __META_CONFIG__ run_pass "$TEST_KEY" rose macro --non-interactive --fix --config=../config file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<'__OUT__' -[T] rose.macros.DefaultTransforms: changes: 4 +[T] metomi.rose.macros.DefaultTransforms: changes: 4 namelist:bar(1)=baz=0 enabled -> trig-ignored namelist:bar(1)=fred=0 diff --git a/t/rose-macro/12-custom-change.t b/t/rose-macro/12-custom-change.t index f90d9332c3..736e28a7c4 100755 --- a/t/rose-macro/12-custom-change.t +++ b/t/rose-macro/12-custom-change.t @@ -34,9 +34,9 @@ init_meta $TEST_DIR/$TEST_SUITE/meta/lib/python/macros/suite.py <<'__MACRO__' #!/usr/bin/env python3 # -*- coding: utf-8 -*- -import rose.macro +import metomi.rose.macro -class SuiteChecker(rose.macro.MacroBase): +class SuiteChecker(metomi.rose.macro.MacroBase): """Suite checker macro.""" def validate(self, config, meta_config=None): @@ -100,10 +100,10 @@ cat >$TEST_DIR/$TEST_SUITE/app/foo/meta/lib/python/macros/foo.py <<'__MACRO__' #!/usr/bin/env python3 # -*- coding: utf-8 -*- -import rose.macro +import metomi.rose.macro -class FooChecker(rose.macro.MacroBase): +class FooChecker(metomi.rose.macro.MacroBase): """Foo checker macro.""" def validate(self, config, meta_config=None): @@ -136,10 +136,10 @@ cat >$TEST_DIR/$TEST_SUITE/app/bar/meta/lib/python/macros/bar.py <<'__MACRO__' #!/usr/bin/env python3 # -*- coding: utf-8 -*- -import rose.macro +import metomi.rose.macro -class BarChecker(rose.macro.MacroBase): +class BarChecker(metomi.rose.macro.MacroBase): """Bar checker macro.""" def validate(self, config, meta_config=None): @@ -164,40 +164,40 @@ file_cmp $TEST_KEY.out $TEST_KEY.out <<'__OUT__' [INFO] app/bar/rose-app.conf [V] bar.BarChecker # Bar checker macro. -[V] rose.macros.DefaultValidators +[V] metomi.rose.macros.DefaultValidators # Runs all the default checks, such as compulsory checking. [T] bar.BarChecker # Bar checker macro. -[T] rose.macros.DefaultTransforms +[T] metomi.rose.macros.DefaultTransforms # Runs all the default fixers, such as trigger fixing. [R] bar.BarChecker # Bar checker macro. [INFO] app/foo/rose-app.conf -[V] rose.macros.DefaultValidators +[V] metomi.rose.macros.DefaultValidators # Runs all the default checks, such as compulsory checking. [V] foo.FooChecker # Foo checker macro. -[T] rose.macros.DefaultTransforms +[T] metomi.rose.macros.DefaultTransforms # Runs all the default fixers, such as trigger fixing. [T] foo.FooChecker # Foo checker macro. [R] foo.FooChecker # Foo checker macro. [INFO] rose-suite.conf -[V] rose.macros.DefaultValidators +[V] metomi.rose.macros.DefaultValidators # Runs all the default checks, such as compulsory checking. [V] suite.SuiteChecker # Suite checker macro. -[T] rose.macros.DefaultTransforms +[T] metomi.rose.macros.DefaultTransforms # Runs all the default fixers, such as trigger fixing. [T] suite.SuiteChecker # Suite checker macro. [R] suite.SuiteChecker # Suite checker macro. [INFO] rose-suite.info -[V] rose.macros.DefaultValidators +[V] metomi.rose.macros.DefaultValidators # Runs all the default checks, such as compulsory checking. -[T] rose.macros.DefaultTransforms +[T] metomi.rose.macros.DefaultTransforms # Runs all the default fixers, such as trigger fixing. __OUT__ #------------------------------------------------------------------------------- @@ -208,40 +208,40 @@ file_cmp $TEST_KEY.out $TEST_KEY.out <<'__OUT__' [INFO] app/bar/rose-app.conf [V] bar.BarChecker # Bar checker macro. -[V] rose.macros.DefaultValidators +[V] metomi.rose.macros.DefaultValidators # Runs all the default checks, such as compulsory checking. [T] bar.BarChecker # Bar checker macro. -[T] rose.macros.DefaultTransforms +[T] metomi.rose.macros.DefaultTransforms # Runs all the default fixers, such as trigger fixing. [R] bar.BarChecker # Bar checker macro. [INFO] app/foo/rose-app.conf -[V] rose.macros.DefaultValidators +[V] metomi.rose.macros.DefaultValidators # Runs all the default checks, such as compulsory checking. [V] foo.FooChecker # Foo checker macro. -[T] rose.macros.DefaultTransforms +[T] metomi.rose.macros.DefaultTransforms # Runs all the default fixers, such as trigger fixing. [T] foo.FooChecker # Foo checker macro. [R] foo.FooChecker # Foo checker macro. [INFO] rose-suite.conf -[V] rose.macros.DefaultValidators +[V] metomi.rose.macros.DefaultValidators # Runs all the default checks, such as compulsory checking. [V] suite.SuiteChecker # Suite checker macro. -[T] rose.macros.DefaultTransforms +[T] metomi.rose.macros.DefaultTransforms # Runs all the default fixers, such as trigger fixing. [T] suite.SuiteChecker # Suite checker macro. [R] suite.SuiteChecker # Suite checker macro. [INFO] rose-suite.info -[V] rose.macros.DefaultValidators +[V] metomi.rose.macros.DefaultValidators # Runs all the default checks, such as compulsory checking. -[T] rose.macros.DefaultTransforms +[T] metomi.rose.macros.DefaultTransforms # Runs all the default fixers, such as trigger fixing. __OUT__ #------------------------------------------------------------------------------- @@ -249,11 +249,11 @@ __OUT__ TEST_KEY=$TEST_KEY_BASE-list-all-app-foo run_pass $TEST_KEY rose macro -C $TEST_DIR/$TEST_SUITE/app/foo file_cmp $TEST_KEY.out $TEST_KEY.out <<'__OUT__' -[V] rose.macros.DefaultValidators +[V] metomi.rose.macros.DefaultValidators # Runs all the default checks, such as compulsory checking. [V] foo.FooChecker # Foo checker macro. -[T] rose.macros.DefaultTransforms +[T] metomi.rose.macros.DefaultTransforms # Runs all the default fixers, such as trigger fixing. [T] foo.FooChecker # Foo checker macro. @@ -265,11 +265,11 @@ __OUT__ TEST_KEY=$TEST_KEY_BASE-list-all-app-foo-subdir run_pass $TEST_KEY rose macro -C $TEST_DIR/$TEST_SUITE/app/foo/meta file_cmp $TEST_KEY.out $TEST_KEY.out <<'__OUT__' -[V] rose.macros.DefaultValidators +[V] metomi.rose.macros.DefaultValidators # Runs all the default checks, such as compulsory checking. [V] foo.FooChecker # Foo checker macro. -[T] rose.macros.DefaultTransforms +[T] metomi.rose.macros.DefaultTransforms # Runs all the default fixers, such as trigger fixing. [T] foo.FooChecker # Foo checker macro. @@ -284,40 +284,40 @@ file_cmp $TEST_KEY.out $TEST_KEY.out <<'__OUT__' [INFO] app/bar/rose-app.conf [V] bar.BarChecker # Bar checker macro. -[V] rose.macros.DefaultValidators +[V] metomi.rose.macros.DefaultValidators # Runs all the default checks, such as compulsory checking. [T] bar.BarChecker # Bar checker macro. -[T] rose.macros.DefaultTransforms +[T] metomi.rose.macros.DefaultTransforms # Runs all the default fixers, such as trigger fixing. [R] bar.BarChecker # Bar checker macro. [INFO] app/foo/rose-app.conf -[V] rose.macros.DefaultValidators +[V] metomi.rose.macros.DefaultValidators # Runs all the default checks, such as compulsory checking. [V] foo.FooChecker # Foo checker macro. -[T] rose.macros.DefaultTransforms +[T] metomi.rose.macros.DefaultTransforms # Runs all the default fixers, such as trigger fixing. [T] foo.FooChecker # Foo checker macro. [R] foo.FooChecker # Foo checker macro. [INFO] rose-suite.conf -[V] rose.macros.DefaultValidators +[V] metomi.rose.macros.DefaultValidators # Runs all the default checks, such as compulsory checking. [V] suite.SuiteChecker # Suite checker macro. -[T] rose.macros.DefaultTransforms +[T] metomi.rose.macros.DefaultTransforms # Runs all the default fixers, such as trigger fixing. [T] suite.SuiteChecker # Suite checker macro. [R] suite.SuiteChecker # Suite checker macro. [INFO] rose-suite.info -[V] rose.macros.DefaultValidators +[V] metomi.rose.macros.DefaultValidators # Runs all the default checks, such as compulsory checking. -[T] rose.macros.DefaultTransforms +[T] metomi.rose.macros.DefaultTransforms # Runs all the default fixers, such as trigger fixing. __OUT__ #------------------------------------------------------------------------------- @@ -328,16 +328,16 @@ file_cmp $TEST_KEY.err $TEST_KEY.err <<'__ERR__' [V] bar.BarChecker: issues: 1 env=BAR=| bar < pub -[V] rose.macros.DefaultValidators: issues: 1 +[V] metomi.rose.macros.DefaultValidators: issues: 1 env=BAR=| Not an integer: '|' [V] foo.FooChecker: issues: 1 env=FOO=baz Not foo enough -[V] rose.macros.DefaultValidators: issues: 1 +[V] metomi.rose.macros.DefaultValidators: issues: 1 env=FOO=baz Not an integer: 'baz' -[V] rose.macros.DefaultValidators: issues: 2 +[V] metomi.rose.macros.DefaultValidators: issues: 2 env=ANSWER=quarante deux Not an integer: 'quarante deux' (opts=optional)env=ANSWER=caurenta y dos @@ -347,7 +347,7 @@ file_cmp $TEST_KEY.err $TEST_KEY.err <<'__ERR__' Incorrectanswer (opts=optional)env=ANSWER=caurenta y dos Incorrectanswer -[V] rose.macros.DefaultValidators: issues: 3 +[V] metomi.rose.macros.DefaultValidators: issues: 3 =owner=None Variable set as compulsory, but not in configuration. =project=None @@ -368,15 +368,15 @@ run_pass $TEST_KEY rose macro -C $TEST_DIR/$TEST_SUITE -T -y file_cmp $TEST_KEY.out $TEST_KEY.out <<'__OUT__' [INFO] app/bar/rose-app.conf [T] bar.BarChecker: changes: 0 -[T] rose.macros.DefaultTransforms: changes: 0 +[T] metomi.rose.macros.DefaultTransforms: changes: 0 [INFO] app/foo/rose-app.conf -[T] rose.macros.DefaultTransforms: changes: 0 +[T] metomi.rose.macros.DefaultTransforms: changes: 0 [T] foo.FooChecker: changes: 0 [INFO] rose-suite.conf -[T] rose.macros.DefaultTransforms: changes: 0 +[T] metomi.rose.macros.DefaultTransforms: changes: 0 [T] suite.SuiteChecker: changes: 0 [INFO] rose-suite.info -[T] rose.macros.DefaultTransforms: changes: 2 +[T] metomi.rose.macros.DefaultTransforms: changes: 2 =owner= Added compulsory option =project= @@ -388,13 +388,13 @@ TEST_KEY=$TEST_KEY_BASE-fix-all-suite run_pass $TEST_KEY rose macro -C $TEST_DIR/$TEST_SUITE -F -y file_cmp $TEST_KEY.out $TEST_KEY.out <<'__OUT__' [INFO] app/bar/rose-app.conf -[T] rose.macros.DefaultTransforms: changes: 0 +[T] metomi.rose.macros.DefaultTransforms: changes: 0 [INFO] app/foo/rose-app.conf -[T] rose.macros.DefaultTransforms: changes: 0 +[T] metomi.rose.macros.DefaultTransforms: changes: 0 [INFO] rose-suite.conf -[T] rose.macros.DefaultTransforms: changes: 0 +[T] metomi.rose.macros.DefaultTransforms: changes: 0 [INFO] rose-suite.info -[T] rose.macros.DefaultTransforms: changes: 2 +[T] metomi.rose.macros.DefaultTransforms: changes: 2 =owner= Added compulsory option =project= @@ -405,7 +405,7 @@ __OUT__ TEST_KEY=$TEST_KEY_BASE-validate-all-suite-only-suite run_fail $TEST_KEY rose macro -C $TEST_DIR/$TEST_SUITE -V --suite-only file_cmp $TEST_KEY.err $TEST_KEY.err <<'__ERR__' -[V] rose.macros.DefaultValidators: issues: 2 +[V] metomi.rose.macros.DefaultValidators: issues: 2 env=ANSWER=quarante deux Not an integer: 'quarante deux' (opts=optional)env=ANSWER=caurenta y dos @@ -415,7 +415,7 @@ file_cmp $TEST_KEY.err $TEST_KEY.err <<'__ERR__' Incorrectanswer (opts=optional)env=ANSWER=caurenta y dos Incorrectanswer -[V] rose.macros.DefaultValidators: issues: 3 +[V] metomi.rose.macros.DefaultValidators: issues: 3 =owner=None Variable set as compulsory, but not in configuration. =project=None @@ -432,7 +432,7 @@ __OUT__ TEST_KEY=$TEST_KEY_BASE-validate-all-suite-only-app-foo run_fail $TEST_KEY rose macro -C $TEST_DIR/$TEST_SUITE/app/foo -V --suite-only file_cmp $TEST_KEY.err $TEST_KEY.err <<'__ERR__' -[V] rose.macros.DefaultValidators: issues: 2 +[V] metomi.rose.macros.DefaultValidators: issues: 2 env=ANSWER=quarante deux Not an integer: 'quarante deux' (opts=optional)env=ANSWER=caurenta y dos @@ -442,7 +442,7 @@ file_cmp $TEST_KEY.err $TEST_KEY.err <<'__ERR__' Incorrectanswer (opts=optional)env=ANSWER=caurenta y dos Incorrectanswer -[V] rose.macros.DefaultValidators: issues: 3 +[V] metomi.rose.macros.DefaultValidators: issues: 3 =owner=None Variable set as compulsory, but not in configuration. =project=None @@ -462,7 +462,7 @@ file_cmp $TEST_KEY.err $TEST_KEY.err <<'__ERR__' [V] foo.FooChecker: issues: 1 env=FOO=baz Not foo enough -[V] rose.macros.DefaultValidators: issues: 1 +[V] metomi.rose.macros.DefaultValidators: issues: 1 env=FOO=baz Not an integer: 'baz' __ERR__ @@ -473,7 +473,7 @@ __OUT__ TEST_KEY=$TEST_KEY_BASE-transform-all-suite-app-foo run_pass $TEST_KEY rose macro -C $TEST_DIR/$TEST_SUITE/app/foo -T -y file_cmp $TEST_KEY.out $TEST_KEY.out <<'__OUT__' -[T] rose.macros.DefaultTransforms: changes: 0 +[T] metomi.rose.macros.DefaultTransforms: changes: 0 [T] foo.FooChecker: changes: 0 __OUT__ #------------------------------------------------------------------------------- @@ -481,7 +481,7 @@ __OUT__ TEST_KEY=$TEST_KEY_BASE-fix-all-suite-app-foo run_pass $TEST_KEY rose macro -C $TEST_DIR/$TEST_SUITE/app/foo -F -y file_cmp $TEST_KEY.out $TEST_KEY.out <<'__OUT__' -[T] rose.macros.DefaultTransforms: changes: 0 +[T] metomi.rose.macros.DefaultTransforms: changes: 0 __OUT__ #------------------------------------------------------------------------------- rm -r $TEST_DIR/$TEST_SUITE diff --git a/t/rose-macro/lib/custom_macro_change.py b/t/rose-macro/lib/custom_macro_change.py index 1e7079fa22..0f1ad3fa0c 100644 --- a/t/rose-macro/lib/custom_macro_change.py +++ b/t/rose-macro/lib/custom_macro_change.py @@ -19,10 +19,10 @@ # along with Rose. If not, see . # ----------------------------------------------------------------------------- -import rose.macro +import metomi.rose.macro -class LogicalTransformer(rose.macro.MacroBase): +class LogicalTransformer(metomi.rose.macro.MacroBase): """Test class to change the value of a boolean environment variable.""" @@ -33,10 +33,10 @@ def transform(self, config, meta_config=None): self.reports = [] if config.get(["env", "TRANSFORM_SWITCH"]) is not None: value = config.get(["env", "TRANSFORM_SWITCH"]).value - if value == rose.TYPE_BOOLEAN_VALUE_FALSE: - new_value = rose.TYPE_BOOLEAN_VALUE_TRUE + if value == metomi.rose.TYPE_BOOLEAN_VALUE_FALSE: + new_value = metomi.rose.TYPE_BOOLEAN_VALUE_TRUE else: - new_value = rose.TYPE_BOOLEAN_VALUE_FALSE + new_value = metomi.rose.TYPE_BOOLEAN_VALUE_FALSE config.set(["env", "TRANSFORM_SWITCH"], new_value) info = self.WARNING_CHANGED_VALUE.format(value, new_value) self.add_report("env", "TRANSFORM_SWITCH", value, info) diff --git a/t/rose-macro/lib/custom_macro_change_arg.py b/t/rose-macro/lib/custom_macro_change_arg.py index 5a812cd84e..0ac1198142 100644 --- a/t/rose-macro/lib/custom_macro_change_arg.py +++ b/t/rose-macro/lib/custom_macro_change_arg.py @@ -19,7 +19,7 @@ # along with Rose. If not, see . # ----------------------------------------------------------------------------- -from rose.macro import MacroBase +from metomi.rose.macro import MacroBase class ArgumentTransformer(MacroBase): diff --git a/t/rose-macro/lib/custom_macro_change_bad.py b/t/rose-macro/lib/custom_macro_change_bad.py index dd162732e8..eb170cdc97 100644 --- a/t/rose-macro/lib/custom_macro_change_bad.py +++ b/t/rose-macro/lib/custom_macro_change_bad.py @@ -19,10 +19,10 @@ # along with Rose. If not, see . # ----------------------------------------------------------------------------- -import rose.macro +import metomi.rose.macro -class InvalidCommentsTransformer(rose.macro.MacroBase): +class InvalidCommentsTransformer(metomi.rose.macro.MacroBase): """Test class to return invalid comments.""" @@ -40,7 +40,7 @@ def transform(self, config, meta_config=None): return config, self.reports -class InvalidConfigKeysTransformer(rose.macro.MacroBase): +class InvalidConfigKeysTransformer(metomi.rose.macro.MacroBase): """Test class to return invalid comments.""" @@ -55,7 +55,7 @@ def transform(self, config, meta_config=None): return config, self.reports -class InvalidConfigObjectTransformer(rose.macro.MacroBase): +class InvalidConfigObjectTransformer(metomi.rose.macro.MacroBase): """Test class to return invalid configs.""" @@ -65,7 +65,7 @@ def transform(self, config, meta_config=None): return None, self.reports -class InvalidStateTransformer(rose.macro.MacroBase): +class InvalidStateTransformer(metomi.rose.macro.MacroBase): """Test class to return an invalid state.""" @@ -82,7 +82,7 @@ def transform(self, config, meta_config=None): return config, self.reports -class InvalidValueTransformer(rose.macro.MacroBase): +class InvalidValueTransformer(metomi.rose.macro.MacroBase): """Test class to return an invalid value.""" diff --git a/t/rose-macro/lib/custom_macro_check.py b/t/rose-macro/lib/custom_macro_check.py index cd5c72ba1c..9c12bd331c 100644 --- a/t/rose-macro/lib/custom_macro_check.py +++ b/t/rose-macro/lib/custom_macro_check.py @@ -22,10 +22,10 @@ import http.client -import rose.macro +import metomi.rose.macro -class URLChecker(rose.macro.MacroBase): +class URLChecker(metomi.rose.macro.MacroBase): """Class to check if a URL is valid.""" diff --git a/t/rose-macro/lib/custom_macro_cwd.py b/t/rose-macro/lib/custom_macro_cwd.py index 6ee2dc23b0..b5fcd8ed80 100644 --- a/t/rose-macro/lib/custom_macro_cwd.py +++ b/t/rose-macro/lib/custom_macro_cwd.py @@ -4,10 +4,10 @@ import os -import rose.macro +import metomi.rose.macro -class PrintCwd(rose.macro.MacroBase): +class PrintCwd(metomi.rose.macro.MacroBase): """Upgrade from Apple to Fig.""" diff --git a/t/rose-macro/lib/custom_macro_prompt.py b/t/rose-macro/lib/custom_macro_prompt.py index e34cee05a3..9754fa5af8 100644 --- a/t/rose-macro/lib/custom_macro_prompt.py +++ b/t/rose-macro/lib/custom_macro_prompt.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -import rose.macro +import metomi.rose.macro -class Test(rose.macro.MacroBase): +class Test(metomi.rose.macro.MacroBase): def validate(self, config, meta_config=None, answer=42, optional_config_name=None): diff --git a/t/rose-metadata-check/lib/custom_macro.py b/t/rose-metadata-check/lib/custom_macro.py index 712640c2f3..c2811972af 100644 --- a/t/rose-metadata-check/lib/custom_macro.py +++ b/t/rose-metadata-check/lib/custom_macro.py @@ -19,10 +19,10 @@ # along with Rose. If not, see . # ----------------------------------------------------------------------------- -import rose.macro +import metomi.rose.macro -class LogicalTransformer(rose.macro.MacroBase): +class LogicalTransformer(metomi.rose.macro.MacroBase): """Test class to change the value of a boolean environment variable.""" @@ -33,17 +33,17 @@ def transform(self, config, meta_config=None): self.reports = [] if config.get(["env", "TRANSFORM_SWITCH"]) is not None: value = config.get(["env", "TRANSFORM_SWITCH"]).value - if value == rose.TYPE_BOOLEAN_VALUE_FALSE: - new_value = rose.TYPE_BOOLEAN_VALUE_TRUE + if value == metomi.rose.TYPE_BOOLEAN_VALUE_FALSE: + new_value = metomi.rose.TYPE_BOOLEAN_VALUE_TRUE else: - new_value = rose.TYPE_BOOLEAN_VALUE_FALSE + new_value = metomi.rose.TYPE_BOOLEAN_VALUE_FALSE config.set(["env", "TRANSFORM_SWITCH"], new_value) info = self.WARNING_CHANGED_VALUE.format(value, new_value) self.add_report("env", "TRANSFORM_SWITCH", value, info) return config, self.reports -class LogicalTruthChecker(rose.macro.MacroBase): +class LogicalTruthChecker(metomi.rose.macro.MacroBase): """Test class to check the value of a boolean environment variable.""" @@ -53,7 +53,8 @@ def validate(self, config, meta_config=None): """Check the env switch.""" self.reports = [] node = config.get(["env", "TRANSFORM_SWITCH"], no_ignore=True) - if node is not None and node.value != rose.TYPE_BOOLEAN_VALUE_FALSE: + if node is not None and node.value != \ + metomi.rose.TYPE_BOOLEAN_VALUE_FALSE: info = self.ERROR_NOT_TRUE.format(node.value) self.add_report("env", "TRANSFORM_SWITCH", value, info) return self.reports diff --git a/t/rose-metadata-check/lib/custom_macro_corrupt.py b/t/rose-metadata-check/lib/custom_macro_corrupt.py index 4dc3d0c2c4..e783ab391e 100644 --- a/t/rose-metadata-check/lib/custom_macro_corrupt.py +++ b/t/rose-metadata-check/lib/custom_macro_corrupt.py @@ -19,10 +19,10 @@ # along with Rose. If not, see . # ----------------------------------------------------------------------------- -import rose.macro +import metomi.rose.macro -class LogicalTransformer(rose.macro.MacroBase): +class LogicalTransformer(metomi.rose.macro.MacroBase): """Test class to change the value of a boolean environment variable.""" @@ -33,7 +33,7 @@ def transform(self, config, meta_config=None): self.reports = [] if config.get(["env", "TRANSFORM_SWITCH"]) is not None: value = config.get(["env", "TRANSFORM_SWITCH"]).value - if value == rose.TYPE_BOOLEAN_VALUE_FALSE: + if value == metomi.rose.TYPE_BOOLEAN_VALUE_FALSE: node = config.get(["env", "TRANSFORM_SWITCH"], no_ignore=True) self.reports = [] """Check the env switch.""" @@ -43,18 +43,18 @@ def validate(self, config, meta_config=None): """Test class to check the value of a boolean environment variable.""" -class LogicalTruthChecker(rose.macro.MacroBase): +class LogicalTruthChecker(metomi.rose.macro.MacroBase): return config, self.reports self.add_report("env", "TRANSFORM_SWITCH", value, info) info = self.WARNING_CHANGED_VALUE.format(value, new_value) config.set(["env", "TRANSFORM_SWITCH"], new_value) - new_value = rose.TYPE_BOOLEAN_VALUE_FALSE + new_value = metomi.rose.TYPE_BOOLEAN_VALUE_FALSE else: - new_value = rose.TYPE_BOOLEAN_VALUE_TRUE + new_value = metomi.rose.TYPE_BOOLEAN_VALUE_TRUE node = config.get(["env", "TRANSFORM_SWITCH"], no_ignore=True) - if node is not None and node.value != rose.TYPE_BOOLEAN_VALUE_FALSE: + if node is not None and node.value != metomi.rose.TYPE_BOOLEAN_VALUE_FALSE: info = self.ERROR_NOT_TRUE.format(node.value) self.add_report("env", "TRANSFORM_SWITCH", value, info) return self.reports diff --git a/t/rose-mpi-launch/01-command-inner.t b/t/rose-mpi-launch/01-command-inner.t index f912848bd2..eaedafe6d1 100755 --- a/t/rose-mpi-launch/01-command-inner.t +++ b/t/rose-mpi-launch/01-command-inner.t @@ -28,7 +28,7 @@ TEST_KEY=$TEST_KEY_BASE ROSE_LAUNCHER_ULIMIT_OPTS='-a' \ run_pass "$TEST_KEY" rose mpi-launch echo hello world file_cmp "$TEST_KEY.out" "$TEST_KEY.out" <<__OUT__ -[my-launcher] -n 1 $ROSE_HOME/bin/rose-mpi-launch --inner $(which echo) hello world +[my-launcher] -n 1 $ROSE_HOME_BIN/rose-mpi-launch --inner $(which echo) hello world __OUT__ file_cmp "$TEST_KEY.err" "$TEST_KEY.err" stamp-removed.log sed '/^\[INFO\] YYYY-MM-DDTHHMM export ROSE_TASK_CYCLE_TIME=/p; /^\[INFO\] YYYY-MM-DDTHHMM delete: /!d' \ "stamp-removed.log" >'edited-prune.log' + file_cmp "${TEST_KEY}" 'edited-prune.log' <<__LOG__ [INFO] YYYY-MM-DDTHHMM export ROSE_TASK_CYCLE_TIME=20150101T0000Z [INFO] YYYY-MM-DDTHHMM export ROSE_TASK_CYCLE_TIME=20150102T0000Z diff --git a/t/rose.c3/00-self.t b/t/rose.c3/00-self.t index 1ec2084b9e..ddb417bad4 100755 --- a/t/rose.c3/00-self.t +++ b/t/rose.c3/00-self.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env python3 #------------------------------------------------------------------------------- # Copyright (C) 2012-2019 British Crown (Met Office) & Contributors. # @@ -17,4 +17,5 @@ # You should have received a copy of the GNU General Public License # along with Rose. If not, see . #------------------------------------------------------------------------------- -exec python3 $ROSE_HOME/lib/python/rose/c3.py "$@" +import metomi.rose.c3 as c3 +c3._Test().run() diff --git a/t/rose.config_tree/00-self.t b/t/rose.config_tree/00-self.t index 0907a11c99..a60930e19f 100644 --- a/t/rose.config_tree/00-self.t +++ b/t/rose.config_tree/00-self.t @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env python3 #------------------------------------------------------------------------------- # Copyright (C) 2012-2019 British Crown (Met Office) & Contributors. # @@ -17,4 +17,6 @@ # You should have received a copy of the GNU General Public License # along with Rose. If not, see . #------------------------------------------------------------------------------- -exec python3 $ROSE_HOME/lib/python/rose/config_tree.py "$@" +from metomi.rose.config_tree import _Test + +_Test().run() diff --git a/t/rose.env/00-export.t b/t/rose.env/00-export.t index edb9a71ac1..9f41456123 100755 --- a/t/rose.env/00-export.t +++ b/t/rose.env/00-export.t @@ -19,5 +19,5 @@ #----------------------------------------------------------------------------- . "$(dirname "$0")/test_header" tests 1 -run_pass "${TEST_KEY_BASE}" python3 -m "rose.env" "$@" +run_pass "${TEST_KEY_BASE}" python3 -m "metomi.rose.env" "$@" exit 0 diff --git a/t/rose.popen/00-self.t b/t/rose.popen/00-self.t index abdcb53dcf..281a4e1912 100755 --- a/t/rose.popen/00-self.t +++ b/t/rose.popen/00-self.t @@ -20,5 +20,6 @@ D=$(cd $(dirname $0) && pwd) . $D/test_header tests 1 -run_pass "$TEST_KEY_BASE" python3 $ROSE_HOME/lib/python/rose/popen.py "$@" +POPEN_FILE=$(python -c "import metomi.rose.popen; print(metomi.rose.popen.__file__)") +run_pass "$TEST_KEY_BASE" python3 $POPEN_FILE "$@" exit 0 diff --git a/t/rose.variable/00-trigger.py b/t/rose.variable/00-trigger.py index a3d7871e6e..379c0d87c6 100755 --- a/t/rose.variable/00-trigger.py +++ b/t/rose.variable/00-trigger.py @@ -21,7 +21,7 @@ import sys -import rose.variable +import metomi.rose.variable if __name__ == "__main__": - print(rose.variable.parse_trigger_expression(sys.argv[1])) + print(metomi.rose.variable.parse_trigger_expression(sys.argv[1])) diff --git a/t/rose.variable/00-trigger.t b/t/rose.variable/00-trigger.t index babe652675..ff2b4004a8 100755 --- a/t/rose.variable/00-trigger.t +++ b/t/rose.variable/00-trigger.t @@ -17,7 +17,7 @@ # You should have received a copy of the GNU General Public License # along with Rose. If not, see . #------------------------------------------------------------------------------- -# Test rose.variable trigger parsing. +# Test metomi.rose.variable trigger parsing. #------------------------------------------------------------------------------- . $(dirname $0)/test_header init . # ----------------------------------------------------------------------------- -"""Test rose.variable.array_split.""" +"""Test metomi.rose.variable.array_split.""" import sys -from rose.variable import array_split +from metomi.rose.variable import array_split def main(): diff --git a/t/rosie-checkout/00-basic.t b/t/rosie-checkout/00-basic.t index cdc0d24697..165a132686 100755 --- a/t/rosie-checkout/00-basic.t +++ b/t/rosie-checkout/00-basic.t @@ -20,6 +20,7 @@ # Basic tests for "rosie checkout". #------------------------------------------------------------------------------- . $(dirname $0)/test_header + #------------------------------------------------------------------------------- tests 24 #------------------------------------------------------------------------------- diff --git a/t/rosie-create/00-basic.t b/t/rosie-create/00-basic.t index e7be08432b..1fe1666a4d 100755 --- a/t/rosie-create/00-basic.t +++ b/t/rosie-create/00-basic.t @@ -20,6 +20,7 @@ # Basic tests for "rosie create". #------------------------------------------------------------------------------- . $(dirname $0)/test_header + #------------------------------------------------------------------------------- tests 49 #------------------------------------------------------------------------------- diff --git a/t/rosie-create/01-suite-info-meta.t b/t/rosie-create/01-suite-info-meta.t index 3ac686bea3..7afe36b7d2 100755 --- a/t/rosie-create/01-suite-info-meta.t +++ b/t/rosie-create/01-suite-info-meta.t @@ -20,6 +20,7 @@ # Test "rosie create" with configuration metadata for "rose-suite.info". #------------------------------------------------------------------------------- . $(dirname $0)/test_header + #------------------------------------------------------------------------------- tests 21 #------------------------------------------------------------------------------- diff --git a/t/rosie-create/02-copy-inter.t b/t/rosie-create/02-copy-inter.t index ee67d4b3bf..9392ab2295 100755 --- a/t/rosie-create/02-copy-inter.t +++ b/t/rosie-create/02-copy-inter.t @@ -20,6 +20,7 @@ # Test for "rosie create", inter-repository copies. #------------------------------------------------------------------------------- . "$(dirname "$0")/test_header" + #------------------------------------------------------------------------------- tests 20 #------------------------------------------------------------------------------- diff --git a/t/rosie-delete/00-basic.t b/t/rosie-delete/00-basic.t index 5fabdd15af..8b64842938 100755 --- a/t/rosie-delete/00-basic.t +++ b/t/rosie-delete/00-basic.t @@ -20,6 +20,7 @@ # Basic tests for "rosie delete". #------------------------------------------------------------------------------- . $(dirname $0)/test_header + #------------------------------------------------------------------------------- tests 42 #------------------------------------------------------------------------------- diff --git a/t/rosie-disco/00-basic.t b/t/rosie-disco/00-basic.t index 7b3fc91465..a220becebc 100755 --- a/t/rosie-disco/00-basic.t +++ b/t/rosie-disco/00-basic.t @@ -40,7 +40,7 @@ prefix-default=foo prefix-location.foo=$SVN_URL __ROSE_CONF__ export ROSE_CONF_PATH=$PWD -$ROSE_HOME/sbin/rosa db-create -q +rosa db-create -q #------------------------------------------------------------------------------- rose_ws_init 'rosie' 'disco' if [[ -z "${TEST_ROSE_WS_PORT}" ]]; then @@ -59,6 +59,7 @@ URL_FOO_Q="${URL_FOO}query?" TEST_KEY=$TEST_KEY_BASE-curl-root-trailing-slash run_pass "$TEST_KEY" curl -i "${TEST_ROSE_WS_URL}/" # note: slash at end +cat $TEST_KEY.out >&2 file_grep "$TEST_KEY.out" 'HTTP/.* 200 OK' "$TEST_KEY.out" # The app has been set-up so that a trailing slash, as in the test directly @@ -103,7 +104,7 @@ file_cmp "$TEST_KEY.out" "$TEST_KEY.out" "$TEST_KEY.out.1" #------------------------------------------------------------------------------- for FILE in "$TEST_SOURCE_DIR/$TEST_KEY_BASE/"*.conf; do rosie create -q -y --info-file=$FILE --no-checkout - $ROSE_HOME/sbin/rosa svn-post-commit \ + rosa svn-post-commit \ $PWD/svn/foo $(svnlook youngest $PWD/svn/foo) done #------------------------------------------------------------------------------- @@ -112,7 +113,7 @@ for FILE in "$TEST_SOURCE_DIR/$TEST_KEY_BASE/"*.conf.1; do rosie checkout -q $ID cat <$FILE >$PWD/roses/$ID/rose-suite.info svn ci -q -m 'test' $PWD/roses/$ID - $ROSE_HOME/sbin/rosa svn-post-commit \ + rosa svn-post-commit \ $PWD/svn/foo $(svnlook youngest $PWD/svn/foo) done #------------------------------------------------------------------------------- diff --git a/t/rosie-graph/00-basic.t b/t/rosie-graph/00-basic.t index 1080edc48b..9c1e788986 100755 --- a/t/rosie-graph/00-basic.t +++ b/t/rosie-graph/00-basic.t @@ -60,7 +60,7 @@ touch 'conf/opt/rose-port.conf' cat >repos/foo/hooks/post-commit <<__POST_COMMIT__ #!/bin/bash export ROSE_CONF_PATH=$ROSE_CONF_PATH -$ROSE_HOME/sbin/rosa svn-post-commit --debug "\$@" \\ +rosa svn-post-commit --debug "\$@" \\ 1>$PWD/rosa-svn-post-commit.out 2>$PWD/rosa-svn-post-commit.err echo \$? >$PWD/rosa-svn-post-commit.rc __POST_COMMIT__ @@ -160,7 +160,7 @@ echo "2009-02-13T23:31:38.000000Z" >foo-date-9.txt svnadmin setrevprop $PWD/repos/foo -r 8 svn:date foo-date-8.txt # Setup db. -$ROSE_HOME/sbin/rosa db-create -q +rosa db-create -q #------------------------------------------------------------------------------- # Run ws. diff --git a/t/rosie-hello/00-null.t b/t/rosie-hello/00-null.t index b73cf76b9e..31665045f9 100644 --- a/t/rosie-hello/00-null.t +++ b/t/rosie-hello/00-null.t @@ -25,7 +25,7 @@ tests 2 export ROSE_CONF_PATH= run_fail "${TEST_KEY_BASE}" rosie hello file_cmp "${TEST_KEY_BASE}.err" "${TEST_KEY_BASE}.err" <<'__ERR__' -[rosie-id] settings not defined in site/user configuration. +[metomi.rosie-id] settings not defined in site/user configuration. __ERR__ exit diff --git a/t/rosie-lookup/00-basic.t b/t/rosie-lookup/00-basic.t index ec739a1710..00148ba091 100755 --- a/t/rosie-lookup/00-basic.t +++ b/t/rosie-lookup/00-basic.t @@ -58,7 +58,7 @@ touch 'conf/opt/rose-port.conf' cat >repos/foo/hooks/post-commit <<__POST_COMMIT__ #!/bin/bash export ROSE_CONF_PATH=$ROSE_CONF_PATH -$ROSE_HOME/sbin/rosa svn-post-commit --debug "\$@" \\ +rosa svn-post-commit --debug "\$@" \\ 1>$PWD/rosa-svn-post-commit.out 2>$PWD/rosa-svn-post-commit.err echo \$? >$PWD/rosa-svn-post-commit.rc __POST_COMMIT__ @@ -128,7 +128,7 @@ echo "2009-02-13T23:31:35.000000Z" >foo-date-6.txt svnadmin setrevprop $PWD/repos/foo -r 6 svn:date foo-date-6.txt # Setup db. -$ROSE_HOME/sbin/rosa db-create -q +rosa db-create -q #------------------------------------------------------------------------------- # Run ws. diff --git a/t/rosie-lookup/01-multi.t b/t/rosie-lookup/01-multi.t index df10daec68..927dec173f 100755 --- a/t/rosie-lookup/01-multi.t +++ b/t/rosie-lookup/01-multi.t @@ -43,6 +43,7 @@ mkdir conf cat >conf/rose.conf <<__ROSE_CONF__ opts=port (default) + [rosie-db] repos.bar=$PWD/repos/bar repos.foo=$PWD/repos/foo @@ -79,7 +80,7 @@ __ROSE_SUITE_INFO rosie create -q -y --prefix=bar --info-file=rose-suite.info --no-checkout # Setup DB -$ROSE_HOME/sbin/rosa db-create -q +rosa db-create -q #------------------------------------------------------------------------------- # Run WS diff --git a/t/rosie-lookup/02-unicode.t b/t/rosie-lookup/02-unicode.t index c2d1a46040..903066d03c 100755 --- a/t/rosie-lookup/02-unicode.t +++ b/t/rosie-lookup/02-unicode.t @@ -65,7 +65,7 @@ __ROSE_SUITE_INFO rosie create -q -y --prefix='foo' --info-file='rose-suite.info' --no-checkout # Setup DB -"${ROSE_HOME}/sbin/rosa" db-create -q +rosa db-create -q #------------------------------------------------------------------------------- # Run WS diff --git a/t/rosie-ls/00-basic.t b/t/rosie-ls/00-basic.t index fc6985f41f..518f6cfda2 100755 --- a/t/rosie-ls/00-basic.t +++ b/t/rosie-ls/00-basic.t @@ -113,7 +113,7 @@ __ROSE_SUITE_INFO rosie create -q -y --prefix=bar --info-file=rose-suite.info # Setup DB -$ROSE_HOME/sbin/rosa db-create -q +rosa db-create -q #------------------------------------------------------------------------------- # Run WS diff --git a/t/rosie-ls/01-many.t b/t/rosie-ls/01-many.t index 1a17b8227f..38f1ddcc6d 100755 --- a/t/rosie-ls/01-many.t +++ b/t/rosie-ls/01-many.t @@ -67,7 +67,7 @@ __ROSE_SUITE_INFO done # Setup DB -"${ROSE_HOME}/sbin/rosa" db-create -q +rosa db-create -q #------------------------------------------------------------------------------- # Run WS diff --git a/t/rosie.db/00-query.py b/t/rosie.db/00-query.py index e3a0f5eb35..958971582a 100755 --- a/t/rosie.db/00-query.py +++ b/t/rosie.db/00-query.py @@ -22,8 +22,8 @@ import ast import sys -from rosie.db import DAO -from rosie.db_create import RosieDatabaseInitiator +from metomi.rosie.db import DAO +from metomi.rosie.db_create import RosieDatabaseInitiator from tempfile import NamedTemporaryFile diff --git a/t/rosie.db/00-query.t b/t/rosie.db/00-query.t index a629aa060c..d2c2c19819 100755 --- a/t/rosie.db/00-query.t +++ b/t/rosie.db/00-query.t @@ -17,7 +17,7 @@ # You should have received a copy of the GNU General Public License # along with Rose. If not, see . #------------------------------------------------------------------------------- -# Test rosie.db parsing. +# Test metomi.rosie.db parsing. #------------------------------------------------------------------------------- . $(dirname $0)/test_header TEST_PARSER="python3 $TEST_SOURCE_DIR/$TEST_KEY_BASE.py"