Passed
Push — main ( cb7df0...9a00ee )
by J
01:24
created

devenv.devenv.run_scripts()   B

Complexity

Conditions 7

Size

Total Lines 28
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 17
dl 0
loc 28
rs 8
c 0
b 0
f 0
cc 7
nop 4
1
# -*- coding: utf-8 -*-
2
"""Automate the creation of development environments"""
3
import argparse
4
import os
5
from pathlib import Path
6
from shutil import copytree
7
import subprocess
8
from typing import Dict
9
10
from devenv import SCRIPTS_DIR, VERSION
11
from devenv.utils import check_dir, confirm, print_error
12
13
14
def copy_scripts(
15
    src: Path = Path(__file__).parent / "scripts",
16
    dest: Path = SCRIPTS_DIR,
17
    overwrite: bool = False,
18
    quiet: bool = False,
19
) -> bool:
20
    """Copies scripts from the devenv installation to the local script directory
21
22
    Args:
23
        src: The directory to copy to dest.
24
        dest: The directory to copy to. Must be a filepath ending in "scripts"
25
        overwrite: Overwrite the destination directory if it exists
26
27
    Returns:
28
        bool: True for on successful copy, False if the copy fails
29
    """
30
    try:
31
        copytree(src, dest, dirs_exist_ok=overwrite)
0 ignored issues
show
Bug introduced by
The keyword dirs_exist_ok does not seem to exist for the function call.
Loading history...
32
        if not quiet:
33
            print(f"'{src}' copied to '{dest}'!")
34
    except FileExistsError:
35
        # ask before overwriting
36
        if confirm(f"'{dest}' already exists. Overwrite? [Y/n] "):
37
            return copy_scripts(src, dest, overwrite=True)
38
        return False
39
    return True
40
41
42
def run_scripts(script_dir: Path, lang: str, name: str, quiet: bool = False) -> bool:
43
    """Runs scripts in a given dir
44
45
    Args:
46
        script_dir: The directory to search for scripts
47
        lang: The main language of the project
48
        name: The name of the project
49
50
    Returns:
51
        bool: True for success, False otherwise
52
    """
53
    for script in sorted(script_dir.iterdir()):
54
        if not os.access(script, os.X_OK):
55
            if not quiet:
56
                print_error(f"'{script.name}' is not executable! Skipping.", "WARN")
57
            continue
58
        if script.is_dir():
59
            continue
60
61
        try:
62
            if not quiet:
63
                print(f"Running '{script.name}'...")
64
            subprocess.run([str(script.resolve()), lang, name], check=True)
65
        except subprocess.CalledProcessError as err:
66
            print_error(f"Error running '{script}'!")
67
            print_error(err)
68
            return False
69
    return True
70
71
72
def parse_args() -> argparse.Namespace:
73
    """Parse arguments
74
75
    Returns:
76
        argparse.Namespace: a Namespace with all collected arguments
77
    """
78
    parser: argparse.ArgumentParser = argparse.ArgumentParser(prog="devenv")
79
    parser.add_argument(
80
        "lang", nargs="?", help="the language of the project", default=None
81
    )
82
    parser.add_argument("name", nargs="?", help="the name of the project", default=None)
83
    parser.add_argument(
84
        "--install_scripts", action="store_true", help="install the builtin scripts"
85
    )
86
    parser.add_argument(
87
        "-q", "--quiet", action="store_true", help="supress non-fatal messages"
88
    )
89
    parser.add_argument(
90
        "--scripts_path",
91
        help="the path to a 'scripts' directory",
92
        default=SCRIPTS_DIR,
93
        type=Path,
94
    )
95
    parser.add_argument("--version", action="version", version=f"%(prog)s {VERSION}")
96
    args = parser.parse_args()
97
98
    # throw error when there are no positional args when needed
99
    if not args.install_scripts:
100
        if not args.lang and not args.name:
101
            print_error("the following arguments are required: lang, name")
102
            parser.print_help()
103
            raise SystemExit(1)
104
        if not args.lang:
105
            print_error("the following arguments are required: lang")
106
            parser.print_help()
107
            raise SystemExit(1)
108
        if not args.name:
109
            print_error("the following arguments are required: name")
110
            parser.print_help()
111
            raise SystemExit(1)
112
113
    return args
114
115
116
def main(args: argparse.Namespace):
117
    """Run the program according to arguments provided
118
119
    Args:
120
        args: A Namespace object of arguments to provide
121
    """
122
    if args.install_scripts:
123
        if not copy_scripts(dest=args.scripts_path, quiet=args.quiet):
124
            print_error("Error copying scripts!")  # TODO: test this
0 ignored issues
show
Coding Style introduced by
TODO and FIXME comments should generally be avoided.
Loading history...
125
        if not args.lang or args.name:
126
            raise SystemExit
127
128
    all_dir: Path = args.scripts_path / "all"
129
    lang_dir: Path = args.scripts_path / args.lang
130
    all_running: bool = check_dir(all_dir)
131
    lang_running: bool = check_dir(lang_dir)
132
    script_dirs: Dict[Path, bool] = {all_dir: all_running, lang_dir: lang_running}
133
134
    try:
135
        no_run = 0
136
        for directory, to_run in script_dirs.items():
137
            if not to_run:
138
                if not args.quiet:
139
                    print_error(
140
                        f"Skipping '{directory}', as there are no runnable scripts.",
141
                        "WARN",
142
                    )
143
                no_run += 1
144
                continue
145
            if not run_scripts(directory, args.lang, args.name, args.quiet):
146
                raise SystemExit(1)
147
        if no_run == 2:
148
            err = (
149
                f"Did not run any scripts; both '{all_dir}' and '{lang_dir}' are empty!"
150
            )
151
            raise SystemError(err)
152
    except PermissionError as err:
153
        raise PermissionError from err
154
155
156
if __name__ == "__main__":
157
    main(parse_args())
158