qdbg.cli   A
last analyzed

Complexity

Total Complexity 11

Size/Duplication

Total Lines 106
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 11
eloc 45
dl 0
loc 106
rs 10
c 0
b 0
f 0

3 Functions

Rating   Name   Duplication   Size   Complexity  
B main() 0 45 6
A parse_traceback() 0 19 4
A get_search_url() 0 13 1
1
#!/usr/bin/python3
2
3
import logging
4
import subprocess
5
import sys
6
import webbrowser
7
from urllib.parse import quote_plus
8
from typing import List
9
10
11
class QdbgError(Exception):
12
    pass
13
14
15
def main(args: List[str]) -> None:
16
    """Run a command with special error handling. If the command returns a non-zero
17
    return code, this function will automatically open a new webbrowser tab and
18
    search for the error message.
19
20
    Args:
21
        *args*: a shell command to run
22
23
    Exceptions:
24
        * A QdbgError is raised if no command is provided
25
        * A QdbgError is raised if no default webbrowser if found
26
        * The program exits if a FileNotFoundError occurs
27
28
    Returns:
29
        None
30
    """
31
32
    if len(args) < 1:  # pragma: no cover
33
        logging.error("Qdbg requires a command")
34
        raise QdbgError
35
36
    try:
37
        child_proc = subprocess.run(args=args, check=False, capture_output=True)
38
39
        # On non-zero return code, raise subprocess.CalledProcessError
40
        child_proc.check_returncode()
41
42
        # On success, flush output to stdout
43
        print(child_proc.stdout.decode().rstrip(), flush=True)
44
45
    except subprocess.CalledProcessError:
46
        search_url = get_search_url(cmd=args[0], stderr=child_proc.stderr.decode())
47
48
        if not webbrowser.open_new_tab(url=search_url):
49
            logging.error("Unable to open default browser - is one installed?")
50
            raise QdbgError
51
52
        exit(1)
53
54
    except FileNotFoundError as e:
55
        logging.error(f"Command not found: {e}")
56
        sys.exit(127)
57
58
    except Exception as e:
59
        raise QdbgError(e)
60
61
62
def parse_traceback(stderr: str, from_bottom: bool = True) -> str:
63
    """Return first non-empty line of the string stderr
64
65
    Args:
66
        *stderr*: a string of the stderr output stream
67
        *from_bottom*: when true, search from the end of the stream
68
69
    Returns:
70
        a non-empty string in the traceback (ideally the error message), if it
71
        exists
72
    """
73
    spl = stderr.split("\n")
74
75
    for line in reversed(spl) if from_bottom else iter(spl):
76
        if not line:
77
            continue
78
        return line
79
80
    return ""
81
82
83
def get_search_url(cmd: str, stderr: str) -> str:
84
    """Create a search url. Defaults to the "you" search engine.
85
86
    Args:
87
        *cmd*: the first argument of the command provided to `qdbg`
88
        *stderr*: a string of the stderr output stream
89
90
    Returns:
91
        a parameterized search url
92
    """
93
    err = parse_traceback(stderr=stderr)
94
    search_query = quote_plus(f"{cmd} {err}")
95
    return f"https://you.com/search?q={search_query}"
96
97
98
if __name__ == "__main__":
99
    logging.basicConfig(level=logging.WARN)
100
101
    if len(sys.argv) < 2:  # pragma: no cover
102
        logging.error("Qdbg requires a command")
103
        raise QdbgError
104
105
    main(sys.argv[1:])
106