From 2cf5be072a09d68f0743a50a5573b094964bda16 Mon Sep 17 00:00:00 2001 From: Jairo Llopis Date: Fri, 14 Aug 2020 13:36:51 +0100 Subject: [PATCH] Print all logs to sys.stderr --- copier/main.py | 38 +++++++++++++++++++++++++++++--------- copier/tools.py | 16 +++++++++------- copier/vcs.py | 7 +++++-- tests/test_config.py | 16 ++++++++-------- tests/test_output.py | 43 ++++++++++++++++++++++--------------------- 5 files changed, 73 insertions(+), 47 deletions(-) diff --git a/copier/main.py b/copier/main.py index 31bf03f94..28196f6a5 100644 --- a/copier/main.py +++ b/copier/main.py @@ -147,7 +147,10 @@ def copy( copy_local(conf=conf) except Exception: if conf.cleanup_on_error and not do_diff_update: - print("Something went wrong. Removing destination folder.") + print( + colors.warn | "Something went wrong. Removing destination folder.", + file=sys.stderr, + ) shutil.rmtree(conf.dst_path, ignore_errors=True) raise finally: @@ -269,7 +272,8 @@ def update_diff(conf: ConfigData) -> None: except ProcessExecutionError: print( colors.warn - | "Make sure Git >= 2.24 is installed to improve updates." + | "Make sure Git >= 2.24 is installed to improve updates.", + file=sys.stderr, ) diff = diff_cmd("--inter-hunk-context=0") # Run pre-migration tasks @@ -354,13 +358,19 @@ def render_folder(rel_folder: Path, conf: ConfigData) -> None: return if dst_path.exists(): - printf("identical", rel_path, style=Style.IGNORE, quiet=conf.quiet) + printf( + "identical", + rel_path, + style=Style.IGNORE, + quiet=conf.quiet, + file_=sys.stderr, + ) return if not conf.pretend: make_folder(dst_path) - printf("create", rel_path, style=Style.OK, quiet=conf.quiet) + printf("create", rel_path, style=Style.OK, quiet=conf.quiet, file_=sys.stderr) def render_file( @@ -386,15 +396,25 @@ def render_file( dst_path = conf.dst_path / rel_path if not dst_path.exists(): - printf("create", rel_path, style=Style.OK, quiet=conf.quiet) + printf("create", rel_path, style=Style.OK, quiet=conf.quiet, file_=sys.stderr) elif files_are_identical(src_path, dst_path, content): - printf("identical", rel_path, style=Style.IGNORE, quiet=conf.quiet) + printf( + "identical", + rel_path, + style=Style.IGNORE, + quiet=conf.quiet, + file_=sys.stderr, + ) return elif must_skip(rel_path) or not overwrite_file(conf, dst_path, rel_path): - printf("skip", rel_path, style=Style.WARNING, quiet=conf.quiet) + printf( + "skip", rel_path, style=Style.WARNING, quiet=conf.quiet, file_=sys.stderr + ) return else: - printf("force", rel_path, style=Style.WARNING, quiet=conf.quiet) + printf( + "force", rel_path, style=Style.WARNING, quiet=conf.quiet, file_=sys.stderr + ) if conf.pretend: pass @@ -432,7 +452,7 @@ def overwrite_file(conf: ConfigData, dst_path: Path, rel_path: Path) -> bool: True if the overwrite was forced or the user answered yes, False if skipped by configuration or if the user answered no. """ - printf("conflict", rel_path, style=Style.DANGER, quiet=conf.quiet) + printf("conflict", rel_path, style=Style.DANGER, quiet=conf.quiet, file_=sys.stderr) if conf.force: return True if conf.skip: diff --git a/copier/tools.py b/copier/tools.py index 36512ab20..fefa99f28 100644 --- a/copier/tools.py +++ b/copier/tools.py @@ -3,9 +3,10 @@ import errno import os import shutil +import sys import unicodedata from pathlib import Path -from typing import Any, Dict, List, Optional, Union +from typing import Any, Dict, List, Optional, TextIO, Union import colorama import pathspec @@ -53,6 +54,7 @@ def printf( style: Optional[IntSeq] = None, indent: int = 10, quiet: Union[bool, StrictBool] = False, + file_: TextIO = sys.stdout, ) -> Optional[str]: if quiet: return None # HACK: Satisfy MyPy @@ -62,7 +64,7 @@ def printf( return action + _msg out = style + [action] + Style.RESET + [INDENT, _msg] # type: ignore - print(*out, sep="") + print(*out, sep="", file=file_) return None # HACK: Satisfy MyPy @@ -70,11 +72,11 @@ def printf_exception( e: Exception, action: str, msg: str = "", indent: int = 0, quiet: bool = False ) -> None: if not quiet: - print("") - printf(action, msg=msg, style=Style.DANGER, indent=indent) - print(HLINE) - print(e) - print(HLINE) + print("", file=sys.stderr) + printf(action, msg=msg, style=Style.DANGER, indent=indent, file_=sys.stderr) + print(HLINE, file=sys.stderr) + print(e, file=sys.stderr) + print(HLINE, file=sys.stderr) def required(value: T, **kwargs: Any) -> T: diff --git a/copier/vcs.py b/copier/vcs.py index f551e4287..5281b0af0 100644 --- a/copier/vcs.py +++ b/copier/vcs.py @@ -1,6 +1,6 @@ """Utilities related to VCS.""" - import re +import sys import tempfile from pathlib import Path @@ -75,7 +75,10 @@ def checkout_latest_tag(local_repo: StrOrPath, use_prereleases: OptBool = False) try: latest_tag = str(sorted_tags[0]) except IndexError: - print(colors.warn | "No git tags found in template; using HEAD as ref") + print( + colors.warn | "No git tags found in template; using HEAD as ref", + file=sys.stderr, + ) latest_tag = "HEAD" git("checkout", "--force", latest_tag) git("submodule", "update", "--checkout", "--init", "--recursive", "--force") diff --git a/tests/test_config.py b/tests/test_config.py index 705f729e6..efafdacc3 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -47,13 +47,13 @@ def test_invalid_yaml(capsys): conf_path = Path("tests", "demo_invalid", "copier.yml") with pytest.raises(InvalidConfigFileError): load_yaml_data(conf_path) - out, _ = capsys.readouterr() - assert "INVALID CONFIG FILE" in out - assert str(conf_path) in out + _, err = capsys.readouterr() + assert "INVALID CONFIG FILE" in err + assert str(conf_path) in err @pytest.mark.parametrize( - "conf_path,flags,check_out", + "conf_path,flags,check_err", ( ("tests/demo_invalid", {"_warning": False}, lambda x: "INVALID" in x), ("tests/demo_invalid", {"quiet": True}, lambda x: x == ""), @@ -63,12 +63,12 @@ def test_invalid_yaml(capsys): ("tests/demo_transclude_invalid_multi/demo", {}, None), ), ) -def test_invalid_config_data(conf_path, flags, check_out, capsys): +def test_invalid_config_data(conf_path, flags, check_err, capsys): with pytest.raises(InvalidConfigFileError): load_config_data(conf_path, **flags) - if check_out: - out, _ = capsys.readouterr() - assert check_out(out) + if check_err: + _, err = capsys.readouterr() + assert check_err(err) def test_config_data_empty(): diff --git a/tests/test_output.py b/tests/test_output.py index 302d6246a..6c7cde406 100644 --- a/tests/test_output.py +++ b/tests/test_output.py @@ -5,43 +5,44 @@ def test_output(capsys, tmp_path): render(tmp_path, quiet=False) - out, _ = capsys.readouterr() - assert re.search(r"create[^\s]* config\.py", out) - assert re.search(r"create[^\s]* pyproject\.toml", out) - assert re.search(r"create[^\s]* doc[/\\]images[/\\]nslogo\.gif", out) + _, err = capsys.readouterr() + assert re.search(r"create[^\s]* config\.py", err) + assert re.search(r"create[^\s]* pyproject\.toml", err) + assert re.search(r"create[^\s]* doc[/\\]images[/\\]nslogo\.gif", err) def test_output_pretend(capsys, tmp_path): render(tmp_path, quiet=False, pretend=True) - out, _ = capsys.readouterr() - assert re.search(r"create[^\s]* config\.py", out) - assert re.search(r"create[^\s]* pyproject\.toml", out) - assert re.search(r"create[^\s]* doc[/\\]images[/\\]nslogo\.gif", out) + _, err = capsys.readouterr() + assert re.search(r"create[^\s]* config\.py", err) + assert re.search(r"create[^\s]* pyproject\.toml", err) + assert re.search(r"create[^\s]* doc[/\\]images[/\\]nslogo\.gif", err) def test_output_force(capsys, tmp_path): render(tmp_path) - out, _ = capsys.readouterr() + capsys.readouterr() render(tmp_path, quiet=False, force=True) - out, _ = capsys.readouterr() - assert re.search(r"conflict[^\s]* config\.py", out) - assert re.search(r"force[^\s]* config\.py", out) - assert re.search(r"identical[^\s]* pyproject\.toml", out) - assert re.search(r"identical[^\s]* doc[/\\]images[/\\]nslogo\.gif", out) + _, err = capsys.readouterr() + assert re.search(r"conflict[^\s]* config\.py", err) + assert re.search(r"force[^\s]* config\.py", err) + assert re.search(r"identical[^\s]* pyproject\.toml", err) + assert re.search(r"identical[^\s]* doc[/\\]images[/\\]nslogo\.gif", err) def test_output_skip(capsys, tmp_path): render(tmp_path) - out, _ = capsys.readouterr() + capsys.readouterr() render(tmp_path, quiet=False, skip=True) - out, _ = capsys.readouterr() - assert re.search(r"conflict[^\s]* config\.py", out) - assert re.search(r"skip[^\s]* config\.py", out) - assert re.search(r"identical[^\s]* pyproject\.toml", out) - assert re.search(r"identical[^\s]* doc[/\\]images[/\\]nslogo\.gif", out) + _, err = capsys.readouterr() + assert re.search(r"conflict[^\s]* config\.py", err) + assert re.search(r"skip[^\s]* config\.py", err) + assert re.search(r"identical[^\s]* pyproject\.toml", err) + assert re.search(r"identical[^\s]* doc[/\\]images[/\\]nslogo\.gif", err) def test_output_quiet(capsys, tmp_path): render(tmp_path, quiet=True) - out, _ = capsys.readouterr() + out, err = capsys.readouterr() assert out == "" + assert err == ""