|
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