cmd.py 4.43 KB
Newer Older
Romain Bignon's avatar
Romain Bignon committed
1 2
# -*- coding: utf-8 -*-

3
# Copyright (C) 2011 Romain Bignon, Laurent Bachelier
Romain Bignon's avatar
Romain Bignon committed
4
#
Romain Bignon's avatar
Romain Bignon committed
5
# This file is part of assnet.
6
#
Romain Bignon's avatar
Romain Bignon committed
7
# assnet is free software: you can redistribute it and/or modify
8
# it under the terms of the GNU Affero General Public License as published by
9 10
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
Romain Bignon's avatar
Romain Bignon committed
11
#
Romain Bignon's avatar
Romain Bignon committed
12
# assnet is distributed in the hope that it will be useful,
Romain Bignon's avatar
Romain Bignon committed
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU Affero General Public License for more details.
Romain Bignon's avatar
Romain Bignon committed
16
#
17
# You should have received a copy of the GNU Affero General Public License
Romain Bignon's avatar
Romain Bignon committed
18
# along with assnet. If not, see <http://www.gnu.org/licenses/>.
19

Romain Bignon's avatar
Romain Bignon committed
20 21 22 23

import sys
import getpass
import re
24 25 26
import os
import locale
from tempfile import NamedTemporaryFile
Romain Bignon's avatar
Romain Bignon committed
27 28


29
__all__ = ['ConsolePart', 'Command']
Romain Bignon's avatar
Romain Bignon committed
30 31


32 33 34 35 36
# TODO change name
class ConsolePart(object):
    # shell escape strings
    BOLD   = ''
    NC     = ''    # no color
Romain Bignon's avatar
Romain Bignon committed
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65

    def ask(self, question, default=None, masked=False, regexp=None, choices=None):
        """
        Ask a question to user.

        @param question  text displayed (str)
        @param default  optional default value (str)
        @param masked  if True, do not show typed text (bool)
        @param regexp  text must match this regexp (str)
        @return  entered text by user (str)
        """

        is_bool = False

        if choices:
            question = u'%s (%s)' % (question, '/'.join(
                [s for s in (choices.iterkeys() if isinstance(choices, dict) else choices)]))
        if default is not None:
            if isinstance(default, bool):
                question = u'%s (%s/%s)' % (question, 'Y' if default else 'y', 'n' if default else 'N')
                choices = ('y', 'n', 'Y', 'N')
                default = 'y' if default else 'n'
                is_bool = True
            else:
                question = u'%s [%s]' % (question, default)

        if masked:
            question = u'%s (hidden input)' % question

66
        question += u': '
Romain Bignon's avatar
Romain Bignon committed
67 68 69

        correct = False
        while not correct:
70 71 72
            if masked:
                line = getpass.getpass(question)
            else:
73
                sys.stdout.write(question.encode(sys.stdout.encoding or locale.getpreferredencoding()))
74 75 76 77 78 79 80
                sys.stdout.flush()
                line = sys.stdin.readline()
                if len(line) == 0:
                    raise EOFError()
                else:
                    line = line.rstrip('\r\n')

Romain Bignon's avatar
Romain Bignon committed
81 82 83 84 85 86 87 88 89 90 91 92
            if not line and default is not None:
                line = default
            if isinstance(line, str):
                line = line.decode('utf-8')
            correct = (not regexp or re.match(unicode(regexp), unicode(line))) and \
                      (not choices or unicode(line) in
                       [unicode(s) for s in (choices.iterkeys() if isinstance(choices, dict) else choices)])

        if is_bool:
            return line.lower() == 'y'
        else:
            return line
93

94
    def acquire_input(self, content=None):
95
        editor = os.getenv('EDITOR', 'vi')
96
        if sys.stdin.isatty() and editor:
97 98 99 100 101 102 103 104
            with NamedTemporaryFile() as f:
                filename = f.name
                if content is not None:
                    f.write(content)
                    f.flush()
                os.system("%s %s" % (editor, filename))
                f.seek(0)
                text = f.read()
105 106 107 108 109 110 111
        else:
            if sys.stdin.isatty():
                print 'Reading content from stdin... Type ctrl-D ' \
                          'from an empty line to stop.'
            text = sys.stdin.read()
        return text.decode(sys.stdin.encoding or locale.getpreferredencoding())

Laurent Bachelier's avatar
Laurent Bachelier committed
112

113 114 115 116 117 118 119 120 121 122 123
class Command(ConsolePart):
    DESCRIPTION = None
    WORKDIR = True

    def cmd(self, args):
        raise NotImplementedError()

    @staticmethod
    def configure_parser(parser):
        return

124
    def __init__(self, storage, working_dir, butt):
125
        self.storage = storage
126
        self.working_dir = working_dir
127
        self.butt = butt
128 129

    def run(self, args):
130
        if self.WORKDIR and not self.storage:
Romain Bignon's avatar
Romain Bignon committed
131
            print >>sys.stderr, 'Error: Not a assnet working directory.'
132
            print >>sys.stderr, 'Please use "%s init"' % sys.argv[0]
133
            return 1
134 135

        return self.cmd(args)
136

Laurent Bachelier's avatar
Laurent Bachelier committed
137

138 139 140 141 142 143
class CommandParent(object):
    DESCRIPTION = None

    @staticmethod
    def configure_parser(parser):
        return