From 323c99b5bdf3685c0b9af28d50ad6d1dffecb376 Mon Sep 17 00:00:00 2001 From: David Hart Date: Sat, 12 Oct 2024 10:06:51 -0600 Subject: [PATCH] refactor: Convert command-line interface to click instead of argparse (#54) * refactor: convert to click from argparse * test: correct the tests so that results are returned * refactor: put back version and copyright cli options and test them * test: change path to join abspath --- .github/workflows/executable.yml | 37 +++ docs/conf.py | 2 +- docs/requirements.txt | 2 +- docs/um/running-sansmic.rst | 17 +- examples/basic.ipynb | 22 +- pyproject.toml | 14 +- requirements.txt | 1 + src/python/sansmic/app.py | 521 ++++++++++++------------------- tests/test_app.py | 117 ++++--- 9 files changed, 346 insertions(+), 387 deletions(-) create mode 100644 .github/workflows/executable.yml diff --git a/.github/workflows/executable.yml b/.github/workflows/executable.yml new file mode 100644 index 0000000..dc9af6d --- /dev/null +++ b/.github/workflows/executable.yml @@ -0,0 +1,37 @@ +name: Standalone Executable + +on: + push: + tags: + - '*' + +permissions: + contents: read + +jobs: + build_wheels: + name: Build executable 📦 for windows + runs-on: windows-latest + steps: + - name: Harden Runner + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 + with: + egress-policy: audit + + - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 + + - name: Set up Python + uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0 + with: + python-version: 3.11 + + - name: Install development dependencies + run: | + python3 -m pip install -r requirements-exe.txt + python3 setup.py build_ext -i + python3 setup.py py2exe >> $GITHUB_STEP_SUMMARY + + - uses: actions/upload-artifact@604373da6381bf24206979c74d06a550515601b9 # v4.4.1 + with: + name: standalone + path: dist/*.exe diff --git a/docs/conf.py b/docs/conf.py index 5f0674f..5f29a7a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -40,7 +40,7 @@ "sphinx.ext.intersphinx", "sphinx.ext.githubpages", "sphinx_design", - "sphinxarg.ext", + "sphinx_click", "sphinxcontrib.bibtex", ] diff --git a/docs/requirements.txt b/docs/requirements.txt index eeb0fde..f1d6835 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -4,4 +4,4 @@ sphinx_design==0.6.1 sphinxcontrib-bibtex==2.6.3 breathe==4.35.0 exhale==0.3.7 -sphinx-argparse==0.5.2 +sphinx-click==6.0.0 diff --git a/docs/um/running-sansmic.rst b/docs/um/running-sansmic.rst index dce08be..0bc303c 100644 --- a/docs/um/running-sansmic.rst +++ b/docs/um/running-sansmic.rst @@ -5,21 +5,12 @@ The following include all available output options, but note that not all option available if you have not installed the appropriate packages (see Advanced Installation). -``sansmic`` ------------ - -.. argparse:: - :module: sansmic.app - :func: _main_parser +.. _sansmic: +.. click:: sansmic.app:run :prog: sansmic - -``sansmic-convert`` -------------------- - -.. argparse:: - :module: sansmic.app - :func: _convert_parser +.. _sansmic-convert: +.. click:: sansmic.app:convert :prog: sansmic-convert diff --git a/examples/basic.ipynb b/examples/basic.ipynb index c8f7895..f9e5f85 100644 --- a/examples/basic.ipynb +++ b/examples/basic.ipynb @@ -13,7 +13,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -57,7 +57,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -77,7 +77,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -109,7 +109,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -133,7 +133,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -149,7 +149,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -244,7 +244,7 @@ "## Running from the command line\n", "Jupyter is not a great way to demonstrate how to run sansmic from the command line; however, it is still possible to do so. The following code simulates the execution of the following command:\n", "\n", - "``sansmic old.dat -o cmdlineTest --no-json --no-hdf``\n", + "``sansmic old.dat -o cmdlineTest --no-hdf``\n", "\n", "Try adding \"-v\" or \"-vv\" at the end of the list to see the difference." ] @@ -258,16 +258,14 @@ "import sansmic.app\n", "\n", "\n", - "\n", - "resultsCmdLine = sansmic.app.main(\n", + "resultsCmdLine = sansmic.app.run(\n", " [\n", " \"old.dat\",\n", " \"-o\",\n", " \"cmdlineTest\",\n", - " \"--no-json\",\n", " \"--no-hdf\",\n", " ],\n", - " ret=True,\n", + " standalone_mode=False,\n", ")" ] }, @@ -294,7 +292,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ diff --git a/pyproject.toml b/pyproject.toml index e15f129..931d1a9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ authors = [ { name = "SANSMIC Authors (see AUTHORS.md)" }, ] maintainers = [{ name = "David Hart", email = "david.hart@sandia.gov" }] -dependencies = ["numpy", "pandas", 'tomli; python_version < "3.11"'] +dependencies = ["numpy", "pandas", 'tomli; python_version < "3.11"', "click"] classifiers = [ "Development Status :: 4 - Beta", "License :: OSI Approved :: BSD License", @@ -47,7 +47,7 @@ docs = [ 'sphinxcontrib-bibtex', 'breathe', 'exhale', - 'sphinx-argparse', + 'sphinx-click', ] [project.urls] @@ -56,7 +56,7 @@ Issues = "https://github.com/sandialabs/sansmic/issues" Repository = "https://github.com/sandialabs/sansmic.git" [project.scripts] -sansmic = "sansmic.app:main" +sansmic = "sansmic.app:run" sansmic-convert = "sansmic.app:convert" [build-system] @@ -124,11 +124,11 @@ default = "semantic-release " [tool.semantic_release.commit_parser_options] allowed_tags = [ # Non-source code changes (no change to version; omit from CHANGELOG by default) - "build", # Changes to build system or external dependencies (omit from CHANGELOG - manually add if needed) - "ci", # Changes to CI configuration files and scripts (omit from CHANGELOG - manually add if needed) - "chore", # Other changes that don't touch the source code (omit from CHANGELOG - manually add if needed) + "build", # Changes to build system or external dependencies (omit from CHANGELOG - manually add if needed) + "ci", # Changes to CI configuration files and scripts (omit from CHANGELOG - manually add if needed) + "chore", # Other changes that don't touch the source code (omit from CHANGELOG - manually add if needed) # Changes to test suites (no change to version; include in CHANGELOG by default) - "test", # Adding missing tests or correcting existing tests (does not change package code) + "test", # Adding missing tests or correcting existing tests (does not change package code) # Changes to source code (possible change to version; include in CHANGELOG by default) "fix", # A bug fix (a patch-level version change) "perf", # A code change that improves performance (at-least patch-level, possibly minor version change) diff --git a/requirements.txt b/requirements.txt index c66fc15..04c06da 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,4 @@ numpy==2.1.2 pandas==2.2.3 pybind11==2.13.6 setuptools==75.1.0 +click==8.1.7 diff --git a/src/python/sansmic/app.py b/src/python/sansmic/app.py index 12b7455..b764a4b 100644 --- a/src/python/sansmic/app.py +++ b/src/python/sansmic/app.py @@ -18,11 +18,12 @@ from os.path import splitext import sys +import click from numpy import round from pip._vendor.rich.progress import Progress import sansmic.io -try: +try: # pragma: no cover import h5py except ImportError as e: h5py = e @@ -31,269 +32,170 @@ logger = logging.getLogger("sansmic") -class AboutAction(Action): - """Argparse action to print out version, copyright and/or license information from the package __init__""" - - def __call__(self, parser, namespace, values, option_string=None): - from sansmic import __license__, __copyright__, __version__ - - if option_string == "--license": - print("sansmic", __version__) - print() - print(__license__) - elif option_string == "--copyright": - print("sansmic", __version__) - print() - print(__copyright__) - elif option_string == "--version": - print(parser.prog, __version__) - parser.exit(0) - - @classmethod - def add_to_parser(cls, parser: ArgumentParser): - """Add the --version, --copyright and --license arguments to a parser.""" - parser.add_argument( - "--version", - default=None, - nargs=0, - action=cls, - help="Display version number and exit.", - ) - parser.add_argument( - "--copyright", - default=None, - nargs=0, - action=cls, - help="Display full copyright details and exit.", - ) - parser.add_argument( - "--license", - default=False, - nargs=0, - action=cls, - help="Display full license details and exit.", - ) - - -def _get_verbosity(args): - """Set the logger level for sansmic.""" - level = logging.WARNING - if args.verbose: - level = logging.INFO - elif args.quiet: - level = logging.ERROR - if args.debug: - level = logging.DEBUG - logger.setLevel(level) - if args.debug: - args.verbose = 3 - return 3 - return args.verbose - - -def _main_parser(defaults=False): - from sansmic import __version__ - - if not defaults: - kwargs = dict(default=None) - else: - kwargs = dict() - parser = ArgumentParser( - prog="sansmic", - description="Simulate leaching in an underground salt cavern due to ordinary solution mining, product withdrawal, or product fill.", - ) - AboutAction.add_to_parser(parser) - parser.add_argument( - "datafile", - metavar="INPUTFILE", - help="the SANSMIC input file to use; if extensansmic.ion is '.dat', --toml is implied", - ) - - outp = parser.add_argument_group( - "Output options", - """Default options: ``--csv --tst --no-hdf --no-json --no-old-out``""", - ) - outp.add_argument( - "-o", - "--output", - dest="prefix", - help="change output file prefix; if used, --toml is implied", - default=None, - ) - - wwo = outp.add_mutually_exclusive_group() - wwo.add_argument( - "--toml", - default=None, - action="store_true", - help="Create a TOML scenario file.", - ) - wwo.add_argument( - "--no-toml", - dest="toml", - action="store_false", - help="Don't create a TOML scenario file.", - **kwargs, - ) - - wwo = outp.add_mutually_exclusive_group() - wwo.add_argument( - "--csv", - action="store_true", - default=True if defaults else None, - help="Create CSV results files.", - ) - wwo.add_argument( - "--no-csv", - dest="csv", - action="store_false", - help="Don't create CSV results files.", - **kwargs, - ) - - wwo = outp.add_mutually_exclusive_group() - wwo.add_argument( - "--hdf", - default=False if defaults else None, - action="store_true", - help="Create an HDF5 formatted results file.", - ) - wwo.add_argument( - "--no-hdf", - dest="hdf", - action="store_false", - help="Don't create an HDF5 results file.", - **kwargs, - ) - - wwo = outp.add_mutually_exclusive_group() - wwo.add_argument( - "--json", - default=False if defaults else None, - action="store_true", - help="Create a JSON formatted results file.", - ) - wwo.add_argument( - "--no-json", - dest="json", - action="store_false", - help="Don't create a JSON results file.", - **kwargs, - ) - - wwo = outp.add_mutually_exclusive_group() - wwo.add_argument( - "--tst", - default=True if defaults else None, - action="store_true", - help="Create a daily summary TST text file.", - ) - wwo.add_argument( - "--no-tst", - dest="tst", - action="store_false", - help="Don't create a TST summary file.", - **kwargs, - ) - - wwo = outp.add_mutually_exclusive_group() - wwo.add_argument( - "--old-out", - dest="old_out", - action="store_true", - default=False if defaults else None, - help="Create an old-style SANSMIC OUT file.", - ) - wwo.add_argument( - "--no-old-out", - dest="old_out", - action="store_false", - help="Don't create an old-style OUT file.", - **kwargs, - ) - - outp = parser.add_argument_group( - "Console/stdout/stderr reporting", - "The following options are mutually exclusive", - ) - verb = outp.add_mutually_exclusive_group() - verb.add_argument( - "-v", - "--verbose", - help="increase reporting details", - action="count", - default=0, - ) - verb.add_argument( - "-q", - "--quiet", - help="turn off runtime screen output", - action="store_true", - default=False, - ) - verb.add_argument( - "--debug", - help="turn on debug messages and set max verbosity", - action="store_true", - default=False, - ) - return parser +def print_license(ctx: click.Context, param, value): + """ + Print the license for sansmic. + This is a click option callback function. + """ + if not value or ctx.resilient_parsing: + return + from sansmic import __license__ -def main(args=None, ret=False): - """Command line function to run sansmic. + click.echo_via_pager(__license__) + ctx.exit() - If this function is run from somewhere other than the command line, then - arguments can be passed in to control execution as a list of strings. - The ``ret`` argument can be set to ``True`` to return the results when - using this as a function, also. - Parameters - ---------- - args : argparse.Namespace or list[str] - Arguments as a list of strings that would be used on the command-line. - ret : bool - Should the function return a results object, by default False. +def print_copyright(ctx: click.Context, param, value): + """ + Print the copyright for sansmic. + This is a click option callback function. """ - extra_args = args is not None - if ret or args is not None: - defaults = False - else: - defaults = True - parser = _main_parser(defaults) - args = parser.parse_args(args=args) + if not value or ctx.resilient_parsing: + return + from sansmic import __copyright__ + + click.echo_via_pager(__copyright__) + ctx.exit() + + +@click.group() +def cli(): + pass + + +@click.command() +@click.argument("scenario_file", type=click.Path(exists=True)) +@click.option( + "-o", + "--prefix", + default=None, + help="Prefix for output files. [default: from SCENARIO_FILE]", +) +@click.option( + "--toml/--no-toml", + default=None, + help="Create a TOML scenario file. [default: iff SCENARIO_FILE is .DAT format or PREFIX is set]", +) +@click.option( + "--csv/--no-csv", default=True, help="Create CSV output files.", show_default=True +) +@click.option( + "--hdf/--no-hdf", + default=False, + help=( + "Create an HDF5 output file. [default: no-hdf]" + if not isinstance(h5py, Exception) + else "[DISABLED: h5py not found]" + ), +) +@click.option( + "--tst/--no-tst", + default=True, + help="Create an old-style TST file.", + show_default=True, +) +@click.option( + "--json/--no-json", + default=False, + help="Create a JSON output data file.", + show_default=True, +) +@click.option( + "--oldout/--no-oldout", + default=False, + help="Create an old-style OUT file.", + show_default=True, +) +@click.option( + "-v", "--verbose", count=True, help="Increase log details.", show_default=True +) +@click.option( + "-q", + "--quiet", + is_flag=True, + default=False, + help="Suppress all screen output.", + show_default=True, +) +@click.option( + "--debug", + is_flag=True, + default=False, + help="Output debug information.", + show_default=True, +) +@click.version_option(package_name="sansmic", message="%(version)s") +@click.option( + "--license", + is_flag=True, + callback=print_license, + expose_value=False, + is_eager=True, + help="Show license and exit.", +) +@click.option( + "--copyright", + is_flag=True, + callback=print_copyright, + expose_value=False, + is_eager=True, + help="Print copyright and exit.", +) +def run( + scenario_file, + *, + prefix=None, + toml=None, + csv=True, + hdf=False, + tst=True, + oldout=False, + verbose=0, + quiet=False, + debug=False, + json=False, +): + from sansmic import __version__ - # Wrap the different sections in try/except blocks + if debug: + logger.setLevel(logging.DEBUG) + verbose = 3 + elif verbose == 2: + logger.setLevel(logging.INFO) + elif quiet: + logger.setLevel(logging.ERROR) try: - datafile = args.datafile - prefix = splitext(datafile)[0] if args.prefix is None else args.prefix - verbosity = _get_verbosity(args) - logger.debug("Running sansmic with {}".format(args)) - model = sansmic.io.read_scenario(datafile, warn=not args.quiet) - logger.info("Successfully created scenario from {}".format(datafile)) - if not args.toml: + if prefix is None: + prefix = splitext(scenario_file)[0] + verbosity = verbose + model = sansmic.io.read_scenario(scenario_file, warn=not quiet) + logger.info("Successfully created scenario from {}".format(scenario_file)) + if toml is False: pass - elif ( - args.toml is None and args.prefix is not None - ) or datafile.lower().endswith(".dat"): - args.toml = True - elif args.toml is None and args.prefix is None: - args.toml = False - if args.toml: + elif (toml is None and prefix is not None) or scenario_file.lower().endswith( + ".dat" + ): + toml = True + elif toml is None and prefix is None: + toml = False + if toml: sansmic.io.write_scenario(model, prefix + ".toml") + logger.info("Wrote scenario to {}".format(prefix + ".toml")) except Exception as e: - if extra_args: - raise e - parser.error(str(e)) + logger.critical(str(e)) + raise e logger.debug("Running simulation") - with model.new_simulation(prefix, verbosity, args.tst, args.old_out) as sim: + with model.new_simulation(prefix, verbosity, tst, oldout) as sim: logger.debug("Created new simulation") - if args.quiet or not args.verbose: + if quiet or not verbose: logger.debug("Running in batch mode") - if not args.quiet: - print("Running sansmic in batch mode from ".format(repr(datafile))) + if not quiet: + click.echo( + "Running sansmic in batch mode from {}".format(repr(scenario_file)) + ) sim.run_sim() else: logger.debug("Running in stepwise mode") @@ -309,28 +211,28 @@ def main(args=None, ret=False): ] n_steps = sum(stage_sizes) p_freq = day_size[0] - print( + click.echo( "Running sansmic scenario: {}".format( - datafile if not model.title else model.title + scenario_file if not model.title else model.title ) ) stage = 0 with Progress() as progress: - if args.verbose >= 1: + if verbose >= 1: task = progress.add_task("Progress...", total=n_steps) - if args.verbose >= 2: + if verbose >= 2: task_S = progress.add_task( "[red]Stage {}...".format(stage + 1), total=stage_sizes[stage] ) for stage, step in sim.steps: if last_stage != stage: if stage >= len(model.stages): - if args.verbose >= 1: + if verbose >= 1: progress.update( task, completed=n_steps, ) - if args.verbose >= 2: + if verbose >= 2: progress.update( task_S, completed=n_steps, @@ -340,7 +242,7 @@ def main(args=None, ret=False): last_stage = stage last_step = step p_freq = day_size[stage] - if args.verbose >= 2: + if verbose >= 2: progress.update( task_S, completed=n_steps, @@ -350,21 +252,21 @@ def main(args=None, ret=False): total=stage_sizes[stage], ) else: - if args.verbose >= 1 and (step - last_step) % p_freq == 0: + if verbose >= 1 and (step - last_step) % p_freq == 0: progress.update( task, advance=p_freq, ) - if args.verbose >= 2 and (step - last_step) % p_freq == 0: + if verbose >= 2 and (step - last_step) % p_freq == 0: progress.update( task_S, advance=p_freq, ) logger.debug("Simulation complete") res = sim.results - if not args.quiet: - print("Initial and final results:") - print( + if not quiet: + click.echo("Initial and final results:") + click.echo( (res.df_t_1D.iloc[[0, -1], [1, 3, 13, 15, 19, 20, 21, 26]]).to_string( index=False ) @@ -372,64 +274,57 @@ def main(args=None, ret=False): # Wrap the outputs in a try/except block try: - if args.csv: + if csv: sansmic.io.write_csv_results(res, prefix) - if args.json: + if json: sansmic.io.write_json_results(res, prefix + ".json") - if args.hdf: + if hdf: sansmic.io.write_hdf_results(res, prefix + ".h5") logger.debug("Sansmic complete") - except Exception as e: - if extra_args: - raise e + except Exception as e: # pragma: no cover logger.critical("Error while writing results - some results may be missing") - parser.error(str(e)) - - if ret: - return res - - -def _convert_parser(): - from sansmic import __version__ - - parser = ArgumentParser( - prog=f"sansmic-convert v.{__version__}", - description="Convert from an old-style DAT file to the new TOML format.", - epilog=f"sansmic (c) 2024 NTESS. Use the --copyright or --license flags for full details.", - ) - AboutAction.add_to_parser(parser) - parser.add_argument( - "infile", metavar="OLD_FILE", help="the SANSMIC input file to convert" - ) - parser.add_argument( - "outfile", - metavar="NEW_FILE", - help="the new scenario file to create [extensansmic.ion choices: .toml, .json, .yaml]", - default=None, - ) - parser.add_argument( - "-f", - "--full", - action="store_true", - default=False, - help="Keep all entries, even if they are blank", - ) - return parser - - -def convert(args=None): + raise e + + return res + + +@click.command() +@click.argument("dat_file", type=click.Path(exists=True)) +@click.argument("out_file", type=click.Path(), required=False) +@click.option( + "--full", + is_flag=True, + default=False, + help="Keep all entries even blanks.", + show_default=True, +) +@click.version_option(package_name="sansmic", message="%(version)s") +@click.option( + "--license", + is_flag=True, + callback=print_license, + expose_value=False, + is_eager=True, + help="Show license and exit.", +) +@click.option( + "--copyright", + is_flag=True, + callback=print_copyright, + expose_value=False, + is_eager=True, + help="Print copyright and exit.", +) +def convert(dat_file, out_file=None, full=False): """Command line function to convert a scenario/dat to a new format.""" - extra_args = args is not None - parser = _convert_parser() - args = parser.parse_args(args=args) - infile = args.infile logger.debug("Running sansmic-convert") + if out_file is None: + out_file = click.prompt("Please enter the name of the file to create") try: - model = sansmic.io.read_scenario(infile) - logger.debug("Successfully created scenario from {}".format(infile)) - sansmic.io.write_scenario(model, args.outfile, redundant=args.full) + model = sansmic.io.read_scenario(dat_file) + logger.debug("Successfully created scenario from {}".format(dat_file)) + sansmic.io.write_scenario(model, out_file, redundant=full) except Exception as e: - if extra_args: - raise e - parser.error(str(e)) - logger.debug("Successfully wrote scenario to {}".format(args.outfile)) + logger.critical(str(e)) + raise e + logger.debug("Successfully wrote scenario to {}".format(out_file)) diff --git a/tests/test_app.py b/tests/test_app.py index ec7343b..7eb16fc 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -2,11 +2,13 @@ import glob import os +import subprocess import tempfile import unittest -import subprocess from os.path import abspath, dirname, join +import click +import click.testing import numpy as np import pandas as pd import sansmic @@ -39,83 +41,78 @@ def test_run_app(self): with scenario.new_simulation() as sim: sim.run_sim() res0 = sim.results - res1 = sansmic.app.main( - args=[ + res1 = sansmic.app.run( + [ self.withdrawal_dat, - "--no-toml", "--no-csv", + "--no-toml", "--no-json", "--no-hdf", "--no-tst", - "--no-old-out", + "--no-oldout", ], - ret=True, + standalone_mode=False, ) - res2 = sansmic.app.main( - args=[ + res2 = sansmic.app.run( + [ self.withdrawal_dat, - "-o", + "--prefix", join(self.tempdirname, "app-test"), - "--toml", "--csv", + "--toml", "--json", "--hdf", "--tst", - "--old-out", + "--oldout", ], - ret=False, + standalone_mode=False, ) - res3 = sansmic.app.main( - args=[ + res3 = sansmic.app.run( + [ self.withdrawal_dat, - "--no-toml", "--no-csv", - "--no-json", "--no-hdf", "--no-tst", - "--no-old-out", "-v", ], - ret=True, + standalone_mode=False, ) - res4 = sansmic.app.main( - args=[ + res4 = sansmic.app.run( + [ self.withdrawal_dat, "--no-toml", "--no-csv", "--no-json", "--no-hdf", "--no-tst", - "--no-old-out", - "-v", - "-v", + "--no-oldout", + "-vv", ], - ret=True, + standalone_mode=False, ) - res5 = sansmic.app.main( - args=[ + res5 = sansmic.app.run( + [ self.withdrawal_dat, "--no-toml", "--no-csv", "--no-json", "--no-hdf", "--no-tst", - "--no-old-out", + "--no-oldout", "-q", ], - ret=True, + standalone_mode=False, ) - res6 = sansmic.app.main( - args=[ + res6 = sansmic.app.run( + [ self.withdrawal_dat, "--debug", ], - ret=True, + standalone_mode=False, ) self.assertIsNotNone(res1) - self.assertIsNone(res2) self.assertEqual(len(glob.glob(join(self.tempdirname, "app-test.toml"))), 1) self.assertEqual(len(glob.glob(join(self.tempdirname, "app-test.log"))), 1) @@ -146,30 +143,69 @@ def test_run_app(self): self.assertTrue((res0.df_z_1D == res6.df_z_1D).all().all()) self.assertTrue((res0.df_t_z_2D == res6.df_t_z_2D).all().all()) - with self.assertRaises(FileNotFoundError): - sansmic.app.main( - args=[ + with self.assertRaises( + (click.FileError, click.ClickException, FileNotFoundError) + ): + sansmic.app.run( + [ "thisWontWork.TOML", ], + standalone_mode=False, + ) + + with self.assertRaises( + (click.FileError, click.ClickException, FileNotFoundError) + ): + sansmic.app.run( + [ + self.withdrawal_dat, + "-o", + os.path.abspath(os.path.join("this", "is", "a", "fake.", "path")), + ], + standalone_mode=False, ) + def test_license(self): + """Test the license file echo""" + runner = click.testing.CliRunner() + results = runner.invoke(sansmic.app.run, ["--license"]) + self.assertEqual(results.output.strip(), sansmic.__license__.strip()) + + def test_copyright(self): + """Test the license file echo""" + runner = click.testing.CliRunner() + results = runner.invoke(sansmic.app.run, ["--copyright"]) + self.assertEqual(results.output.strip(), sansmic.__copyright__.strip()) + + def test_version(self): + """Test the license file echo""" + runner = click.testing.CliRunner() + results = runner.invoke(sansmic.app.run, ["--version"]) + self.assertEqual(results.output.strip(), sansmic.__version__.strip()) + def test_convert_app(self): """Test the 'sansmic-convert' command line application.""" scenario0 = sansmic.io.read_scenario(self.withdrawal_dat) scenario0.title = "" - sansmic.app.convert(args=[self.withdrawal_dat, self.withdrawal_toml]) + sansmic.app.convert( + [self.withdrawal_dat, self.withdrawal_toml], standalone_mode=False + ) scenario1 = sansmic.io.read_scenario(self.withdrawal_toml) scenario1.title = "" self.assertEqual(scenario0, scenario1) - sansmic.app.convert(args=[self.withdrawal_dat, self.withdrawal_toml, "--full"]) + sansmic.app.convert( + [self.withdrawal_dat, self.withdrawal_toml, "--full"], standalone_mode=False + ) scenario2 = sansmic.io.read_scenario(self.withdrawal_toml) scenario2.title = "" self.maxDiff = None self.assertDictEqual(scenario0.to_dict(), scenario2.to_dict()) - with self.assertRaises(FileNotFoundError): + with self.assertRaises( + (click.FileError, click.ClickException, FileNotFoundError) + ): sansmic.app.convert( - args=["thisWontWork.TOML", "neither.will.this."], + ["thisWontWork.TOML", "neither.will.this."], standalone_mode=False ) def test_sansmic_cmd(self): @@ -189,8 +225,9 @@ def test_sansmic_cmd(self): result = proc.returncode self.assertTrue("Progress..." in data) self.assertTrue("960000.0" in data) + print(glob.glob(join(self.tempdirname, "sansmic-call-test*"))) self.assertEqual( - len(glob.glob(join(self.tempdirname, "sansmic-call-test*"))), 9 + len(glob.glob(join(self.tempdirname, "sansmic-call-test*"))), 10 ) @classmethod