Completed
Push — master ( a9fd17...8a9714 )
by Jace
7s
created

mine/cli.py (1 issue)

1
#!/usr/bin/env python
2
3 1
"""Command-line interface."""
4
5 1
import sys
6 1
import time
7 1
import argparse
8 1
import subprocess
9 1
import logging
10
11 1
import yorm
12
13 1
from . import CLI, VERSION, DESCRIPTION
14 1
from . import common
15 1
from . import services
16 1
from .data import Data
17 1
from .computer import PLACEHOLDER
18 1
from .application import Application
19 1
from .manager import get_manager
20
21
22 1
log = logging.getLogger(__name__)
23 1
daemon = Application(CLI, filename=CLI)
24
25
26 1
def main(args=None):
27
    """Process command-line arguments and run the program."""
28
29
    # Shared options
30 1
    debug = argparse.ArgumentParser(add_help=False)
31 1
    debug.add_argument('-V', '--version', action='version', version=VERSION)
32 1
    group = debug.add_mutually_exclusive_group()
33 1
    group.add_argument('-v', '--verbose', action='count', default=0,
34
                       help="enable verbose logging")
35 1
    group.add_argument('-q', '--quiet', action='store_const', const=-1,
36
                       dest='verbose', help="only display errors and prompts")
37 1
    shared = {'formatter_class': common.HelpFormatter,
38
              'parents': [debug]}
39
40
    # Build main parser
41 1
    parser = argparse.ArgumentParser(prog=CLI, description=DESCRIPTION,
42
                                     **shared)
43 1
    parser.add_argument('-d', '--daemon', metavar='DELAY', nargs='?', const=300,
44
                        type=int, help="run continuously with delay [seconds]")
45 1
    parser.add_argument('-f', '--file', help="custom settings file path")
46 1
    subs = parser.add_subparsers(help="", dest='command', metavar="<command>")
47
48
    # Build switch parser
49 1
    info = "start applications on another computer"
50 1
    sub = subs.add_parser('switch', description=info.capitalize() + '.',
51
                          help=info, **shared)
52 1
    sub.add_argument('name', nargs='?',
53
                     help="computer to queue for launch (default: current)")
54
55
    # Build close parser
56 1
    info = "close applications on this computer"
57 1
    sub = subs.add_parser('close', description=info.capitalize() + '.',
0 ignored issues
show
Usage of * or ** arguments should usually be done with care.

Generally, there is nothing wrong with usage of * or ** arguments. For readability of the code base, we suggest to not over-use these language constructs though.

For more information, we can recommend this blog post from Ned Batchelder including its comments which also touches this aspect.

Loading history...
58
                          help=info, **shared)
59
60
    # Build edit parser
61 1
    info = "launch the settings file for editing"
62 1
    sub = subs.add_parser('edit', description=info.capitalize() + '.',
63
                          help=info, **shared)
64
65
    # Build clean parser
66 1
    info = "display and delete conflicted files"
67 1
    sub = subs.add_parser('clean', description=info.capitalize() + '.',
68
                          help=info, **shared)
69 1
    sub.add_argument('-f', '--force', action='store_true',
70
                     help="actually delete the conflicted files")
71
72
    # Parse arguments
73 1
    args = parser.parse_args(args=args)
74 1
    kwargs = {'delay': args.daemon}
75 1
    if args.command == 'switch':
76 1
        kwargs['switch'] = args.name if args.name else True
77 1
    elif args.command == 'close':
78
        kwargs['switch'] = False
79 1
    elif args.command == 'edit':
80 1
        kwargs['edit'] = True
81 1
    elif args.command == 'clean':
82 1
        kwargs['delete'] = True
83 1
        kwargs['force'] = args.force
84
85
    # Configure logging
86 1
    common.configure_logging(args.verbose)
87
88
    # Run the program
89 1
    try:
90 1
        log.debug("Running main command...")
91 1
        success = run(path=args.file, **kwargs)
92 1
    except KeyboardInterrupt:
93 1
        msg = "command canceled"
94 1
        if common.verbosity == common.MAX_VERBOSITY:
95 1
            log.exception(msg)
96
        else:
97 1
            log.debug(msg)
98 1
        success = False
99 1
    if success:
100 1
        log.debug("Command succeeded")
101
    else:
102 1
        log.debug("Command failed")
103 1
        sys.exit(1)
104
105
106 1
def run(path=None, cleanup=True, delay=None,
107
        switch=None,
108
        edit=False,
109
        delete=False, force=False):
110
    """Run the program.
111
112
    :param path: custom settings file path
113
    :param cleanup: remove unused items from the config
114
    :param delay: number of seconds to delay before repeating
115
116
    :param switch: computer name to queue for launch
117
118
    :param edit: launch the configuration file for editing
119
120
    :param delete: attempt to delete conflicted files
121
    :param force: actually delete conflicted files
122
123
    """
124 1
    manager = get_manager()
125 1
    root = services.find_root()
126 1
    path = path or services.find_config_path(root=root)
127
128 1
    data = Data()
129 1
    yorm.sync(data, path)
130
131 1
    config = data.config
132 1
    status = data.status
133
134 1
    log.info("Identifying current computer...")
135 1
    computer = config.computers.get_current()
136 1
    log.info("Current computer: %s", computer)
137
138 1
    if edit:
139
        return manager.launch(path)
140 1
    if delete:
141
        return services.delete_conflicts(root, force=force)
142 1
    if log.getEffectiveLevel() >= logging.WARNING:
143
        print("Updating application state...")
144
145 1
    if switch is True:
146
        switch = computer
147 1
    elif switch is False:
148
        switch = PLACEHOLDER
149 1
    elif switch:
150
        switch = config.computers.match(switch)
151 1
    if switch:
152
        data.queue(config, status, switch)
153
154 1
    while True:
155 1
        data.launch(config, status, computer, manager)
156 1
        data.update(config, status, computer, manager)
157
158 1
        if delay is None:
159 1
            break
160
161
        log.info("Delaying %s seconds for files to sync...", delay)
162
        time.sleep(delay)
163
164
        step = 1
165
        elapsed = 0
166
        log.info("Waiting %s seconds for status changes...", delay)
167
        while elapsed < delay and not data.modified:
168
            time.sleep(step)
169
            elapsed += step
170
171
        services.delete_conflicts(root, config_only=True, force=True)
172
173 1
    if cleanup:
174 1
        data.clean(config, status)
175
176 1
    if delay is None:
177 1
        return _restart_daemon(manager)
178
179
    return True
180
181
182 1
def _restart_daemon(manager):
183 1
    cmd = "nohup {} --daemon --verbose >> /tmp/mine.log 2>&1 &".format(CLI)
184 1
    if daemon and not manager.is_running(daemon):
185 1
        log.warning("Daemon is not running, attempting to restart...")
186
187 1
        log.info("$ %s", cmd)
188 1
        subprocess.call(cmd, shell=True)
189 1
        if manager.is_running(daemon):
190 1
            return True
191
192 1
        log.error("Manually start daemon: %s", cmd)
193 1
        return False
194
195 1
    return True
196
197
198
if __name__ == '__main__':  # pragma: no cover (manual test)
199
    main()
200