diff --git a/doc/changelog.d/3679.documentation.md b/doc/changelog.d/3679.documentation.md new file mode 100644 index 00000000000..a79eaec012e --- /dev/null +++ b/doc/changelog.d/3679.documentation.md @@ -0,0 +1 @@ +feat: allow passing strings to the converter from the terminal \ No newline at end of file diff --git a/doc/source/user_guide/convert.rst b/doc/source/user_guide/convert.rst index 51090e155cb..b78d183323f 100644 --- a/doc/source/user_guide/convert.rst +++ b/doc/source/user_guide/convert.rst @@ -17,18 +17,22 @@ file to a Python file named ``python.py``: .. code:: console - $ pymapdl convert mapdl.dat -o python.py + $ echo -ne "/prep7" > mapdl.dat + $ pymapdl convert --file mapdl.dat --output python.py - File mapdl.dat successfully converted to python.py. - -The output argument is completely optional. If you don't specify it, -the ``py`` extension is used for the file that is outputted: +The output argument is optional. If you don't specify it, +the converted code is shown on the terminal: .. code:: console - $ pymapdl convert mapdl.dat + $ pymapdl convert -f mapdl.dat + """Script generated by ansys-mapdl-core version 0.69.dev0""" + + from ansys.mapdl.core import launch_mapdl + mapdl = launch_mapdl(loglevel="WARNING", print_com=True, check_parameter_names=False) + mapdl.prep7() - File mapdl.dat successfully converted to mapdl.py. + mapdl.exit() You can use any option from the :func:`convert_script() ` function. @@ -38,19 +42,26 @@ the script, you can use ``--auto-exit`` argument: .. code:: console - $ pymapdl convert mapdl.dat --auto-exit False - - File mapdl.dat successfully converted to mapdl.py. + $ pymapdl convert -f mapdl.dat --auto-exit False You can skip the imports by setting the ``--add_imports`` option to ``False``: .. code:: console - $ pymapdl convert mapdl.dat --filename_out mapdl.out --add_imports + $ pymapdl convert -f mapdl.dat --output mapdl.out --add_imports False - File mapdl.dat successfully converted to mapdl.out. +You can also skip adding the import, the +:func:`ansys.mapdl.core.launcher.launch_mapdl` call, +and the :meth:`mapdl.exit() ` call by using +the argument ``--only-code`` (``-oc``): + +.. code:: console + + $ pymapdl convert -f mapdl.dat --only-code + mapdl.prep7() + For more information about possible options, use the help command (``pymapdl convert --help``) or the diff --git a/src/ansys/mapdl/core/cli/__init__.py b/src/ansys/mapdl/core/cli/__init__.py index 040b9493c81..e2eedcb92df 100644 --- a/src/ansys/mapdl/core/cli/__init__.py +++ b/src/ansys/mapdl/core/cli/__init__.py @@ -32,14 +32,14 @@ def main(ctx: click.Context): pass - from ansys.mapdl.core.cli.convert import convert + from ansys.mapdl.core.cli.convert import convert as convert_cmd from ansys.mapdl.core.cli.list_instances import list_instances - from ansys.mapdl.core.cli.start import start - from ansys.mapdl.core.cli.stop import stop + from ansys.mapdl.core.cli.start import start as start_cmd + from ansys.mapdl.core.cli.stop import stop as stop_cmd - main.add_command(convert) - main.add_command(start) - main.add_command(stop) + main.add_command(convert_cmd, name="convert") + main.add_command(start_cmd, name="start") + main.add_command(stop_cmd, name="stop") main.add_command(list_instances, name="list") diff --git a/src/ansys/mapdl/core/cli/convert.py b/src/ansys/mapdl/core/cli/convert.py index 0b5b9a56eb5..df2219eefec 100644 --- a/src/ansys/mapdl/core/cli/convert.py +++ b/src/ansys/mapdl/core/cli/convert.py @@ -20,21 +20,10 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -import os -from typing import Any, List +import sys import click -_USING_PIPE: List[bool] = [False] - - -def get_input_source(ctx: click.Context, param: Any, value: Any): - if not value and not click.get_text_stream("stdin").isatty(): - _USING_PIPE[0] = True - return click.get_text_stream("stdin").read().strip() - else: - return value - @click.command( short_help="Convert APDL code to PyMAPDL code.", @@ -44,99 +33,139 @@ def get_input_source(ctx: click.Context, param: Any, value: Any): This example demonstrates the main use of this tool: - $ pymapdl convert mapdl.dat -o python.py + $ pymapdl convert -f mapdl.dat -o python.py - File mapdl.dat successfully converted to python.py. + If you omit the output argument, the converted code is shown on the screen. - The output argument is optional, in which case the "py" extension is used: + You can use any option from ``ansys.mapdl.core.convert.convert_apdl_block`` function: - $ pymapdl convert mapdl.dat + $ pymapdl convert -f mapdl.dat --auto-exit False + \"\"\"Script generated by ansys-mapdl-core version 0.69.dev0\"\"\" - File mapdl.dat successfully converted to mapdl.py. + from ansys.mapdl.core import launch_mapdl + mapdl = launch_mapdl(loglevel="WARNING", print_com=True, check_parameter_names=False) + mapdl.prep7() - You can use any option from ``ansys.mapdl.core.convert.convert_script`` function: + mapdl.exit() - $ pymapdl convert mapdl.dat --auto-exit False + You can skip the imports, and the launching and exit calls if the option `--only-code` (`-oc`) + is given. - File mapdl.dat successfully converted to mapdl.py. + $ pymapdl convert -f mapdl.dat -oc + mapdl.prep7() - $ pymapdl convert mapdl.dat --filename_out mapdl.out --add_imports False + You can also pipe content from files o command line into the converter. - File mapdl.dat successfully converted to mapdl.out.""", + $ echo -ne "/prep7" | pymapdl convert -oc + mapdl.prep7() + + $ echo -ne "/prep7" > my_file.inp + $ pymapdl convert -oc < my_file.inp + mapdl.prep7() +""", +) +@click.option( + "--file", + "-f", + help="Name of the APDL input file to convert to PyMAPDL code.", + type=click.File("r"), + default=sys.stdin, +) +@click.option( + "--output", + "-o", + default=sys.stdout, + type=click.File("at"), + help="Name of the output Python script.", ) -@click.argument("filename_in", callback=get_input_source, required=False) -@click.option("-o", default=None, help="Name of the output Python script.") -@click.option("--filename_out", default=None, help="Name of the output Python script.") @click.option( "--loglevel", + "-ll", default="WARNING", help="Logging level of the ansys object within the script.", ) @click.option( "--auto_exit", + "-ae", default=True, type=bool, help="Adds a line to the end of the script to exit MAPDL. Default ``True``", ) @click.option( - "--line_ending", type=bool, default=None, help="When None, automatically is ``\n.``" + "--line_ending", + "-le", + type=str, + default=None, + help="When None, automatically is ``\n.``", ) @click.option( "--exec_file", + "-e", default=None, type=str, help="Specify the location of the ANSYS executable and include it in the converter output ``launch_mapdl`` call.", ) @click.option( "--macros_as_functions", + "-mf", default=True, type=bool, help="Attempt to convert MAPDL macros to python functions.", ) @click.option( "--use_function_names", + "-fn", default=True, type=bool, help="Convert MAPDL functions to ansys.mapdl.core.Mapdl class methods. When ``True``, the MAPDL command ``K`` will be converted to ``mapdl.k``. When ``False``, it will be converted to ``mapdl.run('k')``.", ) @click.option( "--show_log", + "-sl", default=False, type=bool, help="Print the converted commands using a logger (from ``logging`` Python module).", ) @click.option( "--add_imports", + "-ai", default=True, type=bool, help='If ``True``, add the lines ``from ansys.mapdl.core import launch_mapdl`` and ``mapdl = launch_mapdl(loglevel="WARNING")`` to the beginning of the output file. This option is useful if you are planning to use the output script from another mapdl session. See examples section. This option overrides ``auto_exit``.', ) @click.option( "--comment_solve", + "-cs", default=False, type=bool, help='If ``True``, it will pythonically comment the lines that contain ``"SOLVE"`` or ``"/EOF"``.', ) @click.option( "--cleanup_output", + "-co", default=True, type=bool, help="If ``True`` the output is formatted using ``autopep8`` before writing the file or returning the string. This requires ``autopep8`` to be installed.", ) @click.option( "--header", + "-h", default=True, help="If ``True``, the default header is written in the first line of the output. If a string is provided, this string will be used as header.", ) @click.option( "--print_com", + "-pc", default=True, type=bool, help="Print command ``/COM`` arguments to python console. Defaults to ``True``.", ) @click.option( "--only_commands", + "-oc", default=False, + is_flag=True, + flag_value=True, type=bool, help="""If ``True``, it converts only the commands, meaning that header (``header=False``), imports (``add_imports=False``), @@ -158,13 +187,13 @@ def get_input_source(ctx: click.Context, param: Any, value: Any): ) @click.option( "--check_parameter_names", + "--cpn", default=False, help="""Set MAPDL object to avoid parameter name checks (do not raise leading underscored parameter exceptions). Defaults to `False`.""", ) def convert( - filename_in: str, - o: str, - filename_out: str, + file: str, + output: str, loglevel: str, auto_exit: bool, line_ending: str, @@ -183,64 +212,26 @@ def convert( check_parameter_names: bool, ) -> None: """Convert MAPDL code to PyMAPDL""" - from ansys.mapdl.core.convert import convert_apdl_block, convert_script - - if o: - filename_out = o - - if _USING_PIPE[0]: - code_block = filename_in # from PIPE - click.echo( - convert_apdl_block( - code_block, - loglevel=loglevel, - auto_exit=auto_exit, - line_ending=line_ending, - exec_file=exec_file, - macros_as_functions=macros_as_functions, - use_function_names=use_function_names, - show_log=show_log, - add_imports=add_imports, - comment_solve=comment_solve, - cleanup_output=cleanup_output, - header=header, - print_com=print_com, - only_commands=only_commands, - use_vtk=use_vtk, - clear_at_start=clear_at_start, - check_parameter_names=check_parameter_names, - ) - ) - - else: - convert_script( - filename_in, - filename_out, - loglevel, - auto_exit, - line_ending, - exec_file, - macros_as_functions, - use_function_names, - show_log, - add_imports, - comment_solve, - cleanup_output, - header, - print_com, - only_commands, - use_vtk, - clear_at_start, - check_parameter_names, - ) - - if filename_out: - click.echo( - click.style("Success: ", fg="green") - + f"File {filename_in} successfully converted to {filename_out}." - ) - else: - click.echo( - click.style("Success: ", fg="green") - + f"File {filename_in} successfully converted to {os.path.splitext(filename_in)[0] + '.py'}." - ) + from ansys.mapdl.core.convert import convert_apdl_block + + converted_code = convert_apdl_block( + apdl_strings=file.read(), + loglevel=loglevel, + auto_exit=auto_exit, + line_ending=line_ending, + exec_file=exec_file, + macros_as_functions=macros_as_functions, + use_function_names=use_function_names, + show_log=show_log, + add_imports=add_imports, + comment_solve=comment_solve, + cleanup_output=cleanup_output, + header=header, + print_com=print_com, + only_commands=only_commands, + use_vtk=use_vtk, + clear_at_start=clear_at_start, + check_parameter_names=check_parameter_names, + ) + + click.echo(converted_code, file=output) diff --git a/tests/test_cli.py b/tests/test_cli.py index 41e03705c5b..aa9ba3ccd98 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -20,6 +20,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +import os import re import subprocess from unittest.mock import MagicMock, patch @@ -326,11 +327,7 @@ def test_convert(run_cli, tmpdir): """ ) - output = run_cli(f"convert {input_file} -o {output_file}") - - assert input_file in output - assert output_file in output - assert "successfully converted" in output + run_cli(f"convert -f {input_file} -o {output_file}") with open(output_file, "r") as fid: converted = fid.read() @@ -348,9 +345,14 @@ def test_convert(run_cli, tmpdir): @requires("click") -def test_convert_pipe(): +@pytest.mark.skipif(os.name != "posix", reason="Piping only works on Linux") +@pytest.mark.parametrize("output", [True, False]) +def test_convert_pipe(output): cmd = ["echo", "/prep7"] cmd2 = ["pymapdl", "convert"] + if output: + out_file = "my_output.out" + cmd2.extend(["-o", out_file]) process_echo = subprocess.Popen(cmd, stdout=subprocess.PIPE) process_pymapdl = subprocess.Popen( @@ -361,6 +363,79 @@ def test_convert_pipe(): stdout = process_pymapdl.stdout.read().decode() + if output: + assert os.path.exists(out_file) + with open(out_file, "r") as fid: + stdout = fid.read() + assert "mapdl.prep7" in stdout assert "Script generated by ansys-mapdl-core version" in stdout assert "mapdl.exit()" in stdout + + +DEFAULT_ARGS = { + "apdl_strings": "/prep7\nBLOCK,0,1,0,1,0,1", + "loglevel": "WARNING", + "auto_exit": True, + "line_ending": None, + "exec_file": None, + "macros_as_functions": True, + "use_function_names": True, + "show_log": False, + "add_imports": True, + "comment_solve": False, + "cleanup_output": True, + "header": True, + "print_com": True, + "only_commands": False, + "use_vtk": None, + "clear_at_start": False, + "check_parameter_names": False, +} + + +@requires("click") +@pytest.mark.parametrize( + "arg, value", + ( + ("output", "specific_output"), + ("loglevel", "specific_output"), + ("auto_exit", False), + ("line_ending", "specific_output"), + ("exec_file", "specific_output"), + ("macros_as_functions", False), + ("use_function_names", False), + ("show_log", True), + ("add_imports", False), + ("comment_solve", True), + ("cleanup_output", False), + ("header", False), + ("print_com", False), + ("only_commands", True), + ("use_vtk", True), + ("clear_at_start", True), + ("check_parameter_names", True), + ), +) +@patch("ansys.mapdl.core.convert.convert_apdl_block") +def test_convert_passing(mock_conv, run_cli, tmpdir, arg, value): + mock_conv.return_value = None + + input_file = str(tmpdir.join("input.apdl")) + with open(input_file, "w") as fid: + fid.write("/prep7\nBLOCK,0,1,0,1,0,1") + + default_ = DEFAULT_ARGS.copy() + default_[arg] = value + + if arg not in ["only_commands"]: + run_cli(f"convert -f {input_file} --{arg} {value}") + + else: + run_cli(f"convert -f {input_file} --{arg}") + + mock_conv.assert_called() + kwargs = mock_conv.call_args.kwargs + + for key in DEFAULT_ARGS: + assert kwargs[key] == default_[key] diff --git a/tests/test_convert.py b/tests/test_convert.py index 3f659315bc2..5f04596c11c 100644 --- a/tests/test_convert.py +++ b/tests/test_convert.py @@ -633,7 +633,7 @@ def test_converter_cli(tmpdir, run_cli): with input_file.open("w") as f: f.write(content) - assert run_cli(str(input_file)) == 0 + assert run_cli(f"-f {input_file} -o {output_file}") == 0 assert os.path.exists(output_file) with output_file.open("r") as f: @@ -643,9 +643,33 @@ def test_converter_cli(tmpdir, run_cli): assert "mapdl.exit()" in newcontent assert "launch_mapdl" in newcontent - # This one overwrite the previous file + # This one is appended the previous file assert ( run_cli( + "-f", + str(input_file), + "-o", + str(output_file), + "--auto_exit", + "False", + "--add_imports", + "False", + ) + == 0 + ) + + assert os.path.exists(output_file) + with output_file.open("r") as f: + newcontent = f.read() + + assert newcontent.count("ansys-mapdl-core version") == 2 + + # Deleting file + os.remove(str(output_file)) + + assert ( + run_cli( + "-f", str(input_file), "-o", str(output_file),