|
1 | 1 | # -*- coding: utf-8 -*- |
2 | 2 | """Files, symbolic links, operating system utilities.""" |
3 | 3 | import os |
| 4 | +from argparse import ArgumentTypeError |
| 5 | +from pathlib import Path |
4 | 6 | from shlex import split |
5 | | -from subprocess import call, check_output |
6 | | -from typing import List, Tuple |
| 7 | +from subprocess import PIPE, run |
| 8 | +from time import sleep |
| 9 | +from typing import List |
7 | 10 |
|
8 | 11 | import click |
9 | 12 | import crayons |
10 | | -from plumbum import FG, RETCODE |
| 13 | +from plumbum import FG |
11 | 14 |
|
12 | 15 | from clit import CONFIG, LOGGER, read_config, save_config |
13 | | - |
14 | | -SECTION_SYMLINKS_FILES = "symlinks/files" |
15 | | -SECTION_SYMLINKS_DIRS = "symlinks/dirs" |
16 | | -PYCHARM_APP_FULL_PATH = "/Applications/PyCharm.app/Contents/MacOS/pycharm" |
| 16 | +from clit.constants import SECTION_SYMLINKS_DIRS, SECTION_SYMLINKS_FILES |
17 | 17 |
|
18 | 18 |
|
19 | 19 | @click.command() |
@@ -109,25 +109,6 @@ def message(text, logger_func=LOGGER.warning): |
109 | 109 | message("link created", LOGGER.info) |
110 | 110 |
|
111 | 111 |
|
112 | | -@click.command() |
113 | | -@click.argument("files", nargs=-1) |
114 | | -def pycharm_cli(files): |
115 | | - """Invoke PyCharm on the command line. |
116 | | -
|
117 | | - If a file doesn't exist, call `which` to find out the real location. |
118 | | - """ |
119 | | - full_paths = [] |
120 | | - for possible_file in files: |
121 | | - if os.path.isfile(possible_file): |
122 | | - real_file = os.path.abspath(possible_file) |
123 | | - else: |
124 | | - real_file = check_output(["which", possible_file]).decode().strip() |
125 | | - full_paths.append(real_file) |
126 | | - command_line = [PYCHARM_APP_FULL_PATH] + full_paths |
127 | | - print(crayons.green("Calling PyCharm with {}".format(" ".join(command_line)))) |
128 | | - call(command_line) |
129 | | - |
130 | | - |
131 | 112 | def sync_dir(source_dirs: List[str], destination_dirs: List[str], dry_run: bool = False, kill: bool = False): |
132 | 113 | """Synchronize a source directory with a destination.""" |
133 | 114 | # Import locally, so we get an error only in this function, and not in other functions of this module. |
@@ -168,42 +149,57 @@ def backup_full(ctx, dry_run: bool, kill: bool, pictures: bool): |
168 | 149 | print(ctx.get_help()) |
169 | 150 |
|
170 | 151 |
|
171 | | -@click.command() |
172 | | -@click.option("--delete", "-d", default=False, is_flag=True, help="Delete pytest directory first") |
173 | | -@click.option("--failed", "-f", default=False, is_flag=True, help="Run only failed tests") |
174 | | -@click.option("--count", "-c", default=0, help="Repeat the same test several times") |
175 | | -@click.option("--reruns", "-r", default=0, help="Re-run a failed test several times") |
176 | | -@click.argument("class_names_or_args", nargs=-1) |
177 | | -def pytest_run(delete: bool, failed: bool, count: int, reruns: int, class_names_or_args: Tuple[str]): |
178 | | - """Run pytest with some shortcut options.""" |
179 | | - # Import locally, so we get an error only in this function, and not in other functions of this module. |
180 | | - from plumbum.cmd import time as time_cmd, rm |
181 | | - |
182 | | - if delete: |
183 | | - print(crayons.green("Removing .pytest directory", bold=True)) |
184 | | - rm["-rf", ".pytest"] & FG |
185 | | - |
186 | | - pytest_plus_args = ["pytest", "-vv", "--run-intermittent"] |
187 | | - if reruns: |
188 | | - pytest_plus_args.extend(["--reruns", str(reruns)]) |
189 | | - if failed: |
190 | | - pytest_plus_args.append("--failed") |
191 | | - |
192 | | - if count: |
193 | | - pytest_plus_args.extend(["--count", str(count)]) |
194 | | - |
195 | | - if class_names_or_args: |
196 | | - targets = [] |
197 | | - for name in class_names_or_args: |
198 | | - if "." in name: |
199 | | - parts = name.split(".") |
200 | | - targets.append("{}.py::{}".format("/".join(parts[0:-1]), parts[-1])) |
201 | | - else: |
202 | | - # It might be an extra argument, let's just append it |
203 | | - targets.append(name) |
204 | | - pytest_plus_args.append("-s") |
205 | | - pytest_plus_args.extend(targets) |
206 | | - |
207 | | - print(crayons.green("Running tests: time {}".format(" ".join(pytest_plus_args)), bold=True)) |
208 | | - rv = time_cmd[pytest_plus_args] & RETCODE(FG=True) |
209 | | - exit(rv) |
| 152 | +def shell(command_line, quiet=False, return_lines=False, **kwargs): |
| 153 | + """Print and run a shell command.""" |
| 154 | + if not quiet: |
| 155 | + print("$ {}".format(command_line)) |
| 156 | + if return_lines: |
| 157 | + kwargs.setdefault("stdout", PIPE) |
| 158 | + |
| 159 | + completed_process = run(command_line, shell=True, universal_newlines=True, **kwargs) |
| 160 | + if not return_lines: |
| 161 | + return completed_process |
| 162 | + |
| 163 | + stdout = completed_process.stdout.strip().strip("\n") |
| 164 | + return stdout.split("\n") if stdout else [] |
| 165 | + |
| 166 | + |
| 167 | +def shell_find(command_line, **kwargs): |
| 168 | + """Run a find command using the shell, and return its output as a list.""" |
| 169 | + if not command_line.startswith("find"): |
| 170 | + command_line = f"find {command_line}" |
| 171 | + kwargs.setdefault("quiet", True) |
| 172 | + kwargs.setdefault("check", True) |
| 173 | + return shell(command_line, return_lines=True, **kwargs) |
| 174 | + |
| 175 | + |
| 176 | +def _check_type(full_path, method, msg): |
| 177 | + """Check a path, raise an error if it's not valid.""" |
| 178 | + obj = Path(full_path) |
| 179 | + if not method(obj): |
| 180 | + raise ArgumentTypeError(f"{full_path} is not a valid existing {msg}") |
| 181 | + return obj |
| 182 | + |
| 183 | + |
| 184 | +def existing_directory_type(directory): |
| 185 | + """Convert the string to a Path object, raising an error if it's not a directory. Use with argparse.""" |
| 186 | + return _check_type(directory, Path.is_dir, "directory") |
| 187 | + |
| 188 | + |
| 189 | +def existing_file_type(file): |
| 190 | + """Convert the string to a Path object, raising an error if it's not a file. Use with argparse.""" |
| 191 | + return _check_type(file, Path.is_file, "file") |
| 192 | + |
| 193 | + |
| 194 | +def wait_for_process(process_name: str) -> None: |
| 195 | + """Wait for a process to finish. |
| 196 | +
|
| 197 | + https://stackoverflow.com/questions/1058047/wait-for-any-process-to-finish |
| 198 | + """ |
| 199 | + pid = shell(f"pidof {process_name}", quiet=True, stdout=PIPE).stdout.strip() |
| 200 | + if not pid: |
| 201 | + return |
| 202 | + |
| 203 | + pid_path = Path(f"/proc/{pid}") |
| 204 | + while pid_path.exists(): |
| 205 | + sleep(0.5) |
0 commit comments