Passed
Push — main ( 2d6d90...79017f )
by J
01:57
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 1
"""Automate the creation of development environments"""
3 1
from __future__ import annotations
4 1
import argparse
5 1
import os
6 1
from pathlib import Path
7 1
from shutil import copytree
8 1
import subprocess
9 1
from typing import Dict, List, Union
10
11 1
from rich import print
12
13 1
from devenv import SCRIPTS_DIR, VERSION
14 1
from devenv.utils import check_dir, confirm, print_error
15
16
17 1
def copy_scripts(
18
    src: Path = Path(__file__).parent / "scripts",
19
    dest: Path = SCRIPTS_DIR,
20
    overwrite: bool = False,
21
    quiet: bool = False,
22
) -> bool:
23
    """Copies scripts from the devenv installation to the local script directory
24
25
    Args:
26
        src: The directory to copy to dest.
27
        dest: The directory to copy to. Must be a filepath ending in "scripts"
28
        overwrite: Overwrite the destination directory if it exists
29
30
    Returns:
31
        bool: True for on successful copy, False if the copy fails
32
    """
33 1
    try:
34 1
        copytree(src, dest, dirs_exist_ok=overwrite)
35 1
        if not quiet:
36 1
            print(f"'{src}' copied to '{dest}'!")
37 1
    except FileExistsError:
38
        # ask before overwriting
39 1
        if confirm(f"'{dest}' already exists. Overwrite? [Y/n] "):
40 1
            return copy_scripts(src, dest, overwrite=True)
41 1
        return False
42 1
    return True
43
44
45 1
def list_langs(script_dir: Path) -> Union[List[str], None]:
46
    """Returns a list of all available languages
47
48
    Args:
49
        script_dir: The directory to search for available languages
50
51
    Returns:
52
        list: A list containing any available language
53
    """
54 1
    available_languages: List[str] = []
55 1
    try:
56 1
        for entry in sorted(script_dir.iterdir()):
57 1
            if entry.is_dir():
58 1
                available_languages.append(entry.name)
59 1
        return available_languages
60 1
    except FileNotFoundError as err:
61 1
        print_error(err)
62 1
        return None
63
64
65 1
def run_scripts(script_dir: Path, lang: str, name: str, quiet: bool = False) -> bool:
66
    """Runs scripts in a given dir
67
68
    Args:
69
        script_dir: The directory to search for scripts
70
        lang: The main language of the project
71
        name: The name of the project
72
73
    Returns:
74
        bool: True for success, False otherwise
75
    """
76 1
    for script in sorted(script_dir.iterdir()):
77 1
        if not os.access(script, os.X_OK):
78 1
            if not quiet:
79 1
                print_error(f"'{script.name}' is not executable! Skipping.", "WARN")
80 1
            continue
81 1
        if script.is_file():
82 1
            try:
83 1
                if not quiet:
84 1
                    print(f"Running '{script.name}'...")
85 1
                subprocess.run([str(script.resolve()), lang, name], check=True)
86 1
            except subprocess.CalledProcessError as err:
87 1
                print_error(f"Error running '{script}'!")
88 1
                print_error(err)
89 1
                return False
90 1
    return True
91
92
93 1
def parse_args() -> argparse.Namespace:
94
    """Parse arguments
95
96
    Returns:
97
        argparse.Namespace: a Namespace with all collected arguments
98
    """
99 1
    parser: argparse.ArgumentParser = argparse.ArgumentParser(prog="devenv")
100 1
    parser.add_argument(
101
        "lang", nargs="?", help="the language of the project", default=None
102
    )
103 1
    parser.add_argument("name", nargs="?", help="the name of the project", default=None)
104 1
    parser.add_argument(
105
        "--install_scripts", action="store_true", help="install the builtin scripts"
106
    )
107 1
    parser.add_argument(
108
        "-l",
109
        "--list_langs",
110
        action="store_true",
111
        help="list available language directories",
112
    )
113 1
    parser.add_argument(
114
        "-q", "--quiet", action="store_true", help="supress non-fatal messages"
115
    )
116 1
    parser.add_argument(
117
        "--scripts_path",
118
        help="the path to a 'scripts' directory",
119
        default=SCRIPTS_DIR,
120
        type=Path,
121
    )
122 1
    parser.add_argument("--version", action="version", version=f"%(prog)s {VERSION}")
123 1
    args = parser.parse_args()
124
125
    # throw error when there are no positional args when needed
126 1
    if not args.install_scripts and not args.list_langs:
127 1
        if not args.lang and not args.name:
128 1
            print_error("the following arguments are required: lang, name")
129 1
            parser.print_help()
130 1
            raise SystemExit(1)
131 1
        if not args.lang:
132 1
            print_error("the following arguments are required: lang")
133 1
            parser.print_help()
134 1
            raise SystemExit(1)
135 1
        if not args.name:
136 1
            print_error("the following arguments are required: name")
137 1
            parser.print_help()
138 1
            raise SystemExit(1)
139
140 1
    return args
141
142
143 1
def main(args: argparse.Namespace):
144
    """Run the program according to arguments provided
145
146
    Args:
147
        args: A Namespace object of arguments to provide
148
    """
149 1
    if args.install_scripts or args.list_langs:
150 1
        if args.install_scripts:
151 1
            if not copy_scripts(dest=args.scripts_path, quiet=args.quiet):
152
                print_error("Error copying scripts!")
153 1
        if args.list_langs:
154 1
            if not (langs := list_langs(args.scripts_path)):
0 ignored issues
show
introduced by
invalid syntax (<unknown>, line 154)
Loading history...
155 1
                print_error(
156
                    f"Rerun with `--install_scripts` to populate {args.scripts_path}"
157
                )
158 1
                raise SystemExit(1)
159 1
            print("Available languages are: ", *langs)
160 1
        if not args.lang or args.name:
161 1
            raise SystemExit
162
163 1
    all_dir: Path = args.scripts_path / "all"
164 1
    lang_dir: Path = args.scripts_path / args.lang
165 1
    all_running: bool = check_dir(all_dir)
166 1
    lang_running: bool = check_dir(lang_dir)
167 1
    script_dirs: Dict[Path, bool] = {all_dir: all_running, lang_dir: lang_running}
168
169 1
    try:
170 1
        no_run = 0
171 1
        for directory, to_run in script_dirs.items():
172 1
            if not to_run:
173 1
                if not args.quiet:
174 1
                    print_error(
175
                        f"Skipping '{directory}', as there are no runnable scripts.",
176
                        "WARN",
177
                    )
178 1
                no_run += 1
179 1
                continue
180
            if not run_scripts(directory, args.lang, args.name, args.quiet):
181
                raise SystemExit(1)
182 1
        if no_run == 2:
183 1
            err = (
184
                f"Did not run any scripts; both '{all_dir}' and '{lang_dir}' are empty!"
185
            )
186 1
            raise SystemError(err)
187 1
    except PermissionError as err:
188
        raise PermissionError from err
189
190
191
if __name__ == "__main__":
192
    main(parse_args())
193