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
|
|||
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 |
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.