|
1 | 1 | # -*- coding: utf8 - *- |
2 | | -""" |
3 | | - tmuxp.cli |
4 | | - ~~~~~~~~~ |
| 2 | +"""Command line tool for managing tmux workspaces and tmuxp configurations. |
| 3 | +
|
| 4 | +tmuxp.cli |
| 5 | +~~~~~~~~~ |
| 6 | +
|
| 7 | +:copyright: Copyright 2013 Tony Narlock. |
| 8 | +:license: BSD, see LICENSE for details |
| 9 | +
|
| 10 | +
|
| 11 | +prompt, prompt_bool, prompt_choices |
| 12 | +LICENSE: https://github.com/techniq/flask-script/blob/master/LICENSE |
5 | 13 |
|
6 | | - :copyright: Copyright 2013 Tony Narlock. |
7 | | - :license: BSD, see LICENSE for details |
8 | 14 | """ |
9 | 15 |
|
10 | 16 | import os |
|
16 | 22 | from . import config |
17 | 23 | from distutils.util import strtobool |
18 | 24 | from . import log, util, exc, WorkspaceBuilder, Server |
| 25 | +from .util import ascii_lowercase, input |
19 | 26 | import pkg_resources |
20 | 27 |
|
21 | 28 | __version__ = pkg_resources.require("tmuxp")[0].version |
|
28 | 35 | teamocil_config_dir = os.path.expanduser('~/.teamocil/') |
29 | 36 |
|
30 | 37 |
|
| 38 | +def prompt(name, default=None): |
| 39 | + """Return user input from command line. |
| 40 | +
|
| 41 | + :param name: prompt text |
| 42 | + :param default: default value if no input provided. |
| 43 | + :rtype: string |
| 44 | +
|
| 45 | + """ |
| 46 | + |
| 47 | + prompt = name + (default and ' [%s]' % default or '') |
| 48 | + prompt += name.endswith('?') and ' ' or ': ' |
| 49 | + while True: |
| 50 | + rv = input(prompt) |
| 51 | + if rv: |
| 52 | + return rv |
| 53 | + if default is not None: |
| 54 | + return default |
| 55 | + |
| 56 | + |
| 57 | +def prompt_bool(name, default=False, yes_choices=None, no_choices=None): |
| 58 | + """ |
| 59 | + Return user input from command line and converts to boolean value. |
| 60 | +
|
| 61 | + :param name: prompt text |
| 62 | + :param default: default value if no input provided. |
| 63 | + :param yes_choices: default 'y', 'yes', '1', 'on', 'true', 't' |
| 64 | + :param no_choices: default 'n', 'no', '0', 'off', 'false', 'f' |
| 65 | + :rtype: bool |
| 66 | +
|
| 67 | + """ |
| 68 | + |
| 69 | + yes_choices = yes_choices or ('y', 'yes', '1', 'on', 'true', 't') |
| 70 | + no_choices = no_choices or ('n', 'no', '0', 'off', 'false', 'f') |
| 71 | + |
| 72 | + while True: |
| 73 | + rv = prompt(name, default and yes_choices[0] or no_choices[0]) |
| 74 | + if not rv: |
| 75 | + return default |
| 76 | + if rv.lower() in yes_choices: |
| 77 | + return True |
| 78 | + elif rv.lower() in no_choices: |
| 79 | + return False |
| 80 | + |
| 81 | + |
| 82 | +def prompt_choices(name, choices, default=None, resolve=ascii_lowercase, |
| 83 | + no_choice=('none',)): |
| 84 | + """ |
| 85 | + Return user input from command line from set of provided choices. |
| 86 | +
|
| 87 | + :param name: prompt text |
| 88 | + :param choices: list or tuple of available choices. Choices may be |
| 89 | + single strings or (key, value) tuples. |
| 90 | + :param default: default value if no input provided. |
| 91 | + :param no_choice: acceptable list of strings for "null choice" |
| 92 | + :rtype: str |
| 93 | +
|
| 94 | + """ |
| 95 | + |
| 96 | + _choices = [] |
| 97 | + options = [] |
| 98 | + |
| 99 | + for choice in choices: |
| 100 | + if isinstance(choice, basestring): |
| 101 | + options.append(choice) |
| 102 | + else: |
| 103 | + options.append("%s [%s]" % (choice[1], choice[0])) |
| 104 | + choice = choice[0] |
| 105 | + _choices.append(choice) |
| 106 | + |
| 107 | + while True: |
| 108 | + rv = prompt(name + ' - (%s)' % ', '.join(options), default) |
| 109 | + if not rv: |
| 110 | + return default |
| 111 | + rv = resolve(rv) |
| 112 | + if rv in no_choice: |
| 113 | + return None |
| 114 | + if rv in _choices: |
| 115 | + return rv |
| 116 | + |
| 117 | + |
31 | 118 | class ConfigCompleter(argcomplete.completers.FilesCompleter): |
32 | 119 |
|
33 | 120 | def __call__(self, prefix, **kwargs): |
@@ -75,41 +162,6 @@ def SessionCompleter(prefix, **kwargs): |
75 | 162 | return [s.get('session_name') for s in t._sessions if s.get('session_name').startswith(prefix)] |
76 | 163 |
|
77 | 164 |
|
78 | | -def query_yes_no(question, default="yes"): |
79 | | - """Ask a yes/no question via raw_input() and return their answer. |
80 | | -
|
81 | | - "question" is a string that is presented to the user. |
82 | | - "default" is the presumed answer if the user just hits <Enter>. |
83 | | - It must be "yes" (the default), "no" or None (meaning |
84 | | - an answer is required of the user). |
85 | | -
|
86 | | - The "answer" return value is one of "yes" or "no". |
87 | | -
|
88 | | - License MIT: http://code.activestate.com/recipes/577058/ |
89 | | - """ |
90 | | - valid = {"yes": "yes", "y": "yes", "ye": "yes", |
91 | | - "no": "no", "n": "no"} |
92 | | - if default == None: |
93 | | - prompt = " [y/n] " |
94 | | - elif default == "yes": |
95 | | - prompt = " [Y/n] " |
96 | | - elif default == "no": |
97 | | - prompt = " [y/N] " |
98 | | - else: |
99 | | - raise ValueError("invalid default answer: '%s'" % default) |
100 | | - |
101 | | - while True: |
102 | | - sys.stdout.write(question + prompt) |
103 | | - choice = raw_input().lower() |
104 | | - if default is not None and choice == '': |
105 | | - return strtobool(default) |
106 | | - elif choice in valid.keys(): |
107 | | - return strtobool(valid[choice]) |
108 | | - else: |
109 | | - sys.stdout.write("Please respond with 'yes' or 'no' " |
110 | | - "(or 'y' or 'n').\n") |
111 | | - |
112 | | - |
113 | 165 | def setupLogger(logger=None, level='INFO'): |
114 | 166 | '''setup logging for CLI use. |
115 | 167 |
|
@@ -166,15 +218,15 @@ def build_workspace(config_file, args): |
166 | 218 | builder.build() |
167 | 219 |
|
168 | 220 | if 'TMUX' in os.environ: |
169 | | - if query_yes_no('Already inside TMUX, load session?'): |
| 221 | + if prompt_bool('Already inside TMUX, load session?'): |
170 | 222 | del os.environ['TMUX'] |
171 | 223 | os.execl(tmux_bin, 'tmux', 'switch-client', '-t', sconfig[ |
172 | 224 | 'session_name']) |
173 | 225 |
|
174 | 226 | os.execl(tmux_bin, 'tmux', 'attach-session', '-t', sconfig[ |
175 | 227 | 'session_name']) |
176 | 228 | except exc.TmuxSessionExists as e: |
177 | | - attach_session = query_yes_no(e.message + ' Attach?') |
| 229 | + attach_session = prompt_bool(e.message + ' Attach?') |
178 | 230 |
|
179 | 231 | if 'TMUX' in os.environ: |
180 | 232 | del os.environ['TMUX'] |
@@ -315,57 +367,50 @@ def subcommand_import_tmuxinator(args): |
315 | 367 |
|
316 | 368 |
|
317 | 369 | def subcommand_convert(args): |
318 | | - if args.config: |
319 | | - if '.' in args.config: |
320 | | - args.config.remove('.') |
321 | | - if config.in_cwd(): |
322 | | - args.config.append(config.in_cwd()[0]) |
323 | | - else: |
324 | | - print('No tmuxp configs found in current directory.') |
325 | | - |
326 | | - try: |
327 | | - configfile = args.config |
328 | | - except Exception: |
329 | | - print('Please enter a config') |
330 | 370 |
|
331 | | - file_user = os.path.join(config_dir, configfile) |
332 | | - file_cwd = os.path.join(cwd_dir, configfile) |
333 | | - if os.path.exists(file_cwd) and os.path.isfile(file_cwd): |
334 | | - fullfile = os.path.normpath(file_cwd) |
335 | | - filename, ext = os.path.splitext(file_cwd) |
336 | | - elif os.path.exists(file_user) and os.path.isfile(file_user): |
| 371 | + try: |
| 372 | + configfile = args.config |
| 373 | + except Exception: |
| 374 | + print('Please enter a config') |
| 375 | + |
| 376 | + file_user = os.path.join(config_dir, configfile) |
| 377 | + file_cwd = os.path.join(cwd_dir, configfile) |
| 378 | + if os.path.exists(file_cwd) and os.path.isfile(file_cwd): |
| 379 | + fullfile = os.path.normpath(file_cwd) |
| 380 | + filename, ext = os.path.splitext(file_cwd) |
| 381 | + elif os.path.exists(file_user) and os.path.isfile(file_user): |
| 382 | + |
| 383 | + fullfile = os.path.normpath(file_user) |
| 384 | + filename, ext = os.path.splitext(file_user) |
| 385 | + else: |
| 386 | + logger.error('%s not found.' % configfile) |
| 387 | + return |
337 | 388 |
|
338 | | - fullfile = os.path.normpath(file_user) |
339 | | - filename, ext = os.path.splitext(file_user) |
340 | | - else: |
341 | | - logger.error('%s not found.' % configfile) |
342 | | - return |
343 | | - |
344 | | - if 'json' in ext: |
345 | | - if query_yes_no('convert to <%s> to yaml?' % (fullfile)): |
346 | | - configparser = kaptan.Kaptan() |
347 | | - configparser.import_config(configfile) |
348 | | - newfile = fullfile.replace(ext, '.yaml') |
349 | | - newconfig = configparser.export( |
350 | | - 'yaml', indent=2, default_flow_style=False |
351 | | - ) |
352 | | - if query_yes_no('write config to %s?' % (newfile)): |
353 | | - buf = open(newfile, 'w') |
354 | | - buf.write(newconfig) |
355 | | - buf.close() |
356 | | - print ('written new config to %s' % (newfile)) |
357 | | - elif 'yaml' in ext: |
358 | | - if query_yes_no('convert to <%s> to json?' % (fullfile)): |
359 | | - configparser = kaptan.Kaptan() |
360 | | - configparser.import_config(configfile) |
361 | | - newfile = fullfile.replace(ext, '.json') |
362 | | - newconfig = configparser.export('json', indent=2) |
363 | | - print(newconfig) |
364 | | - if query_yes_no('write config to <%s>?' % (newfile)): |
365 | | - buf = open(newfile, 'w') |
366 | | - buf.write(newconfig) |
367 | | - buf.close() |
368 | | - print ('written new config to <%s>.' % (newfile)) |
| 389 | + if 'json' in ext: |
| 390 | + if prompt_bool('convert to <%s> to yaml?' % (fullfile)): |
| 391 | + configparser = kaptan.Kaptan() |
| 392 | + configparser.import_config(configfile) |
| 393 | + newfile = fullfile.replace(ext, '.yaml') |
| 394 | + newconfig = configparser.export( |
| 395 | + 'yaml', indent=2, default_flow_style=False |
| 396 | + ) |
| 397 | + if prompt_bool('write config to %s?' % (newfile)): |
| 398 | + buf = open(newfile, 'w') |
| 399 | + buf.write(newconfig) |
| 400 | + buf.close() |
| 401 | + print ('written new config to %s' % (newfile)) |
| 402 | + elif 'yaml' in ext: |
| 403 | + if prompt_bool('convert to <%s> to json?' % (fullfile)): |
| 404 | + configparser = kaptan.Kaptan() |
| 405 | + configparser.import_config(configfile) |
| 406 | + newfile = fullfile.replace(ext, '.json') |
| 407 | + newconfig = configparser.export('json', indent=2) |
| 408 | + print(newconfig) |
| 409 | + if prompt_bool('write config to <%s>?' % (newfile)): |
| 410 | + buf = open(newfile, 'w') |
| 411 | + buf.write(newconfig) |
| 412 | + buf.close() |
| 413 | + print ('written new config to <%s>.' % (newfile)) |
369 | 414 |
|
370 | 415 |
|
371 | 416 | def subcommand_attach_session(args): |
|
0 commit comments