|
10 | 10 | import time |
11 | 11 | from os import path |
12 | 12 | from pathlib import Path |
13 | | -from typing import TYPE_CHECKING |
| 13 | +from typing import TYPE_CHECKING, NamedTuple |
14 | 14 |
|
| 15 | +import click |
15 | 16 | import pytest |
16 | 17 |
|
| 18 | +from marimo._cli.export.thumbnail import _split_paths_and_args |
17 | 19 | from marimo._dependencies.dependencies import DependencyManager |
18 | 20 | from marimo._utils import async_path |
19 | 21 | from marimo._utils.platform import is_windows |
@@ -860,3 +862,144 @@ def test_export_pdf_missing_dependencies( |
860 | 862 | stderr = p.stderr.decode() |
861 | 863 | assert "nbconvert" in stderr |
862 | 864 | assert "pip install" in stderr |
| 865 | + |
| 866 | + |
| 867 | +@pytest.mark.skipif( |
| 868 | + not DependencyManager.playwright.has(), |
| 869 | + reason="This test requires playwright.", |
| 870 | +) |
| 871 | +class TestExportThumbnail: |
| 872 | + def test_export_thumbnail(self, temp_marimo_file: str) -> None: |
| 873 | + p = _run_export("thumbnail", temp_marimo_file) |
| 874 | + _assert_success(p) |
| 875 | + |
| 876 | + def test_export_thumbnail_with_args(self, temp_marimo_file: str) -> None: |
| 877 | + p = _run_export("thumbnail", temp_marimo_file, "--", "--foo", "123") |
| 878 | + _assert_success(p) |
| 879 | + |
| 880 | + |
| 881 | +class TestSplitPathArgs: |
| 882 | + """ |
| 883 | + Temporary test suite to illustrate an issue... |
| 884 | +
|
| 885 | + It seems we can't support all of: |
| 886 | + - export options after path (e.g. `marimo export thumbnail nb.py --overwrite) |
| 887 | + - multiple paths (e.g. `marimo export thumbnail nb1.py nb2.py`) |
| 888 | + - notebook arguments (e.g. `marimo export thumbnail nb.py -- arg --foo`) |
| 889 | +
|
| 890 | + By default, click enables allow_interspersed_args, which allows having export options after the path, but |
| 891 | + removes `--` from the arguments. That means the logic of _split_paths_and_args breaks, and every notebook argument |
| 892 | + is instead considered a path. |
| 893 | +
|
| 894 | + We can disable allow_interspersed_args, which keeps `--` in the arguments and fixes _split_paths_and_args. That way, |
| 895 | + we still support multiple paths and notebook arguments, but we can't put export options (e.g. --overwrite) after |
| 896 | + arguments (e.g. notebook or folder path). |
| 897 | +
|
| 898 | + We can remove the _split_paths_and_args logic and simply trust click with default allow_interspersed_args, to |
| 899 | + support export options after path as well as notebook arguments; but then we support only one path at a time. That |
| 900 | + approach seems like it would be the most consistent with other export commands. |
| 901 | + """ |
| 902 | + |
| 903 | + @staticmethod |
| 904 | + def _params(command, expected_paths, expected_args) -> list[NamedTuple]: |
| 905 | + test_command = f"`test_command {' '.join(command)}`" |
| 906 | + |
| 907 | + def _bool_sign(b) -> str: |
| 908 | + return "+" if b else "-" |
| 909 | + |
| 910 | + return [ |
| 911 | + pytest.param( |
| 912 | + command, |
| 913 | + interspersed_args, |
| 914 | + split_path_and_args, |
| 915 | + expected_paths, |
| 916 | + expected_args, |
| 917 | + id=f"{test_command} [{_bool_sign(interspersed_args)}i {_bool_sign(split_path_and_args)}s]", |
| 918 | + ) |
| 919 | + for interspersed_args in [True, False] |
| 920 | + for split_path_and_args in [True, False] |
| 921 | + ] |
| 922 | + |
| 923 | + @pytest.mark.parametrize( |
| 924 | + ( |
| 925 | + "command", |
| 926 | + "allow_interspersed_args", |
| 927 | + "split_paths_and_args", |
| 928 | + "expected_paths", |
| 929 | + "expected_args", |
| 930 | + ), |
| 931 | + [ |
| 932 | + *_params(["foo.py"], ["foo.py"], ()), |
| 933 | + *_params(["foo.py", "bar.py"], ["foo.py", "bar.py"], ()), |
| 934 | + *_params(["--opt", "foo.py"], ["foo.py"], ()), |
| 935 | + *_params(["foo.py", "--opt"], ["foo.py"], ()), |
| 936 | + *_params(["--opt", "foo.py", "bar.py"], ["foo.py", "bar.py"], ()), |
| 937 | + *_params(["foo.py", "--opt", "bar.py"], ["foo.py", "bar.py"], ()), |
| 938 | + *_params(["foo.py", "bar.py", "--opt"], ["foo.py", "bar.py"], ()), |
| 939 | + *_params( |
| 940 | + ["foo.py", "--", "a", "-b", "--c=d"], |
| 941 | + ["foo.py"], |
| 942 | + ("a", "-b", "--c=d"), |
| 943 | + ), |
| 944 | + *_params( |
| 945 | + ["foo.py", "bar.py", "--", "a", "-b", "--c=d"], |
| 946 | + ["foo.py", "bar.py"], |
| 947 | + ("a", "-b", "--c=d"), |
| 948 | + ), |
| 949 | + *_params( |
| 950 | + ["--opt", "foo.py", "--", "a", "-b", "--c=d"], |
| 951 | + ["foo.py"], |
| 952 | + ("a", "-b", "--c=d"), |
| 953 | + ), |
| 954 | + *_params( |
| 955 | + ["foo.py", "--opt", "--", "a", "-b", "--c=d"], |
| 956 | + ["foo.py"], |
| 957 | + ("a", "-b", "--c=d"), |
| 958 | + ), |
| 959 | + *_params( |
| 960 | + ["--opt", "foo.py", "bar.py", "--", "a", "-b", "--c=d"], |
| 961 | + ["foo.py", "bar.py"], |
| 962 | + ("a", "-b", "--c=d"), |
| 963 | + ), |
| 964 | + *_params( |
| 965 | + ["foo.py", "--opt", "bar.py", "--", "a", "-b", "--c=d"], |
| 966 | + ["foo.py", "bar.py"], |
| 967 | + ("a", "-b", "--c=d"), |
| 968 | + ), |
| 969 | + *_params( |
| 970 | + ["foo.py", "bar.py", "--opt", "--", "a", "-b", "--c=d"], |
| 971 | + ["foo.py", "bar.py"], |
| 972 | + ("a", "-b", "--c=d"), |
| 973 | + ), |
| 974 | + ], |
| 975 | + ) |
| 976 | + def test_split_paths_and_args( |
| 977 | + self, |
| 978 | + command: list[str], |
| 979 | + allow_interspersed_args: bool, |
| 980 | + split_paths_and_args: bool, |
| 981 | + expected_paths: list[str], |
| 982 | + expected_args: tuple[str, ...], |
| 983 | + ) -> None: |
| 984 | + @click.command( |
| 985 | + "test_command", |
| 986 | + context_settings={ |
| 987 | + "allow_interspersed_args": allow_interspersed_args |
| 988 | + }, |
| 989 | + ) |
| 990 | + @click.argument("name") |
| 991 | + @click.option("--opt/--no-opt", default=False) |
| 992 | + @click.argument("args", nargs=-1, type=click.UNPROCESSED) |
| 993 | + def test_command( |
| 994 | + name: str, opt: bool, args: tuple[str, ...] |
| 995 | + ) -> tuple[str, tuple[str, ...], bool]: |
| 996 | + return name, args, opt |
| 997 | + |
| 998 | + name, args, _ = test_command.main(command, standalone_mode=False) |
| 999 | + paths, notebook_args = ( |
| 1000 | + _split_paths_and_args(str(name), args) |
| 1001 | + if split_paths_and_args |
| 1002 | + else ([str(name)], args) |
| 1003 | + ) |
| 1004 | + assert paths == expected_paths |
| 1005 | + assert notebook_args == expected_args |
0 commit comments