doorstop.gui.main.main()   A
last analyzed

Complexity

Conditions 3

Size

Total Lines 36
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 3

Importance

Changes 0
Metric Value
eloc 22
dl 0
loc 36
ccs 20
cts 20
cp 1
rs 9.352
c 0
b 0
f 0
cc 3
nop 1
crap 3
1
#!/usr/bin/env python
2
# SPDX-License-Identifier: LGPL-3.0-only
3 1
# type: ignore
4
5 1
"""Graphical interface for Doorstop."""
6 1
7
import argparse
8
import functools
9
import logging
10
import os
11
import sys
12
from unittest.mock import Mock
13
14
from doorstop import common, settings
15 1
from doorstop.common import HelpFormatter, WarningFormatter
16 1
from doorstop.gui import application, widget
17 1
18 1
try:
19 1
    import tkinter as tk
20
    from tkinter import ttk
21 1
except ImportError as _exc:
22 1
    sys.stderr.write("WARNING: {}\n".format(_exc))
23 1
    tk = Mock()
24 1
    ttk = Mock()
25 1
26
27 1
log = common.logger(__name__)
28
29
30 1
def main(args=None):
31
    """Process command-line arguments and run the program."""
32 1
    from doorstop import GUI, VERSION
33
34
    # Shared options
35 1
    debug = argparse.ArgumentParser(add_help=False)
36 1
    debug.add_argument('-V', '--version', action='version', version=VERSION)
37 1
    debug.add_argument(
38
        '-v', '--verbose', action='count', default=0, help="enable verbose logging"
39 1
    )
40 1
    shared = {'formatter_class': HelpFormatter, 'parents': [debug]}
41
    parser = argparse.ArgumentParser(prog=GUI, description=__doc__, **shared)
42
43 1
    # Build main parser
44
    parser.add_argument(
45
        '-j', '--project', metavar="PATH", help="path to the root of the project"
46
    )
47 1
48
    # Parse arguments
49
    args = parser.parse_args(args=args)
50 1
51
    # Configure logging
52
    _configure_logging(args.verbose)
53 1
54 1
    # Run the program
55 1
    try:
56 1
        success = run(args, os.getcwd(), parser.error)
57 1
    except KeyboardInterrupt:
58 1
        log.debug("program interrupted")
59 1
        success = False
60
    if success:
61 1
        log.debug("program exited")
62 1
        return 0
63
    else:
64
        log.debug("program exited with error")
65 1
        return 1
66
67
68 1
def _configure_logging(verbosity=0):
69 1
    """Configure logging using the provided verbosity level (0+)."""
70 1
    # Configure the logging level and format
71 1
    if verbosity == 0:
72 1
        level = settings.VERBOSE_LOGGING_LEVEL
73 1
        default_format = settings.VERBOSE_LOGGING_FORMAT
74 1
        verbose_format = settings.VERBOSE_LOGGING_FORMAT
75 1
    elif verbosity == 1:
76
        level = settings.VERBOSE2_LOGGING_LEVEL
77 1
        default_format = settings.VERBOSE_LOGGING_FORMAT
78 1
        verbose_format = settings.VERBOSE_LOGGING_FORMAT
79 1
    else:
80
        level = settings.VERBOSE2_LOGGING_LEVEL
81
        default_format = settings.TIMED_LOGGING_FORMAT
82 1
        verbose_format = settings.TIMED_LOGGING_FORMAT
83 1
84 1
    # Set a custom formatter
85
    logging.basicConfig(level=level)
86
    formatter = WarningFormatter(default_format, verbose_format)
87 1
    logging.root.handlers[0].setFormatter(formatter)
88
89
90
def run(args, cwd, error):
91
    """Start the GUI.
92
93
    :param args: Namespace of CLI arguments (from this module or the CLI)
94
    :param cwd: current working directory
95 1
    :param error: function to call for CLI errors
96
97 1
    """
98 1
    from doorstop import __project__, __version__
99
100
    # Exit if tkinter is not available
101
    if isinstance(tk, Mock) or isinstance(ttk, Mock):
102
        return error("tkinter is not available")
103
104
    else:
105
106
        root = widget.Tk()
107
        root.title("{} ({})".format(__project__, __version__))
108
109
        from sys import platform as _platform
110
111
        # Load the icon
112
        if _platform in ("linux", "linux2"):
113
            # Linux
114
            from doorstop.gui import resources
115
116
            root.tk.call(
117
                # pylint: disable=protected-access
118
                'wm',
119
                'iconphoto',
120
                root._w,
121
                tk.PhotoImage(data=resources.b64_doorstopicon_png),
122
            )
123
        elif _platform == "darwin":
124
            # macOS
125
            pass  # TODO
126
        elif _platform in ("win32", "win64"):
127
            # Windows
128
            from doorstop.gui import resources
129
            import base64
130
            import tempfile
131
132
            try:
133
                with tempfile.TemporaryFile(
134
                    mode='w+b', suffix=".ico", delete=False
135
                ) as theTempIconFile:
136
                    theTempIconFile.write(
137
                        base64.b64decode(resources.b64_doorstopicon_ico)
138
                    )
139
                    theTempIconFile.flush()
140
                root.iconbitmap(theTempIconFile.name)
141
            finally:
142
                try:
143
                    os.unlink(theTempIconFile.name)
144
                except Exception:  # pylint: disable=W0703
145
                    pass
146
147
        app = application.Application(root, cwd, args.project)
148
149
        root.update()
150
        root.minsize(root.winfo_width(), root.winfo_height())
151
        app.mainloop()
152
153
        return True
154
155
156 View Code Duplication
def _log(func):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
157
    """Log name and arguments."""
158
159
    @functools.wraps(func)
160
    def wrapped(self, *args, **kwargs):
161
        sargs = "{}, {}".format(
162
            ', '.join(repr(a) for a in args),
163
            ', '.join("{}={}".format(k, repr(v)) for k, v in kwargs.items()),
164
        )
165
        msg = "log: {}: {}".format(func.__name__, sargs.strip(", "))
166
        if not isinstance(self, ttk.Frame) or not self.ignore:
167
            log.debug(msg.strip())
168
        return func(self, *args, **kwargs)
169
170
    return wrapped
171
172
173
if __name__ == '__main__':
174
    sys.exit(main())
175