4
4
import sys
5
5
import random
6
6
7
+ __doc__ = \
8
+ """
9
+ The ``console`` object is an interface for user interaction from within a
10
+ ``Node``. Among the input methods are choice lists, plain text input and password
11
+ input.
12
+
13
+ It has output methods that take the terminal size into account, like pagination
14
+ and multi-column display. It takes care of the pseudo terminal underneat.
15
+
16
+ Example:
17
+
18
+ ::
19
+
20
+ class MyNode(Node):
21
+ def do_something(self):
22
+ if self.console.confirm('Should we really do this?', default=True):
23
+ # Do it...
24
+ pass
25
+
26
+ .. note:: When the script runs in a shell that was started with the
27
+ ``--non-interactive`` option, the default options will always be chosen
28
+ automatically.
29
+
30
+ """
31
+
32
+
7
33
class NoInput (Exception ):
8
34
pass
9
35
10
36
11
37
class Console (object ):
38
+ """
39
+ Interface for user interaction from within a ``Node``.
40
+ """
12
41
def __init__ (self , pty ):
13
42
self ._pty = pty
14
43
@@ -18,8 +47,12 @@ def is_interactive(self):
18
47
19
48
def input (self , label , is_password = False , answers = None , default = None ):
20
49
"""
21
- Ask for input. (like raw_input, but nice colored.)
22
- 'answers' can be either None or a list of the accepted answers.
50
+ Ask for plain text input. (Similar to raw_input.)
51
+
52
+ :param is_password: Show stars instead of the actual user input.
53
+ :type is_password: bool
54
+ :param answers: A list of the accepted answers or None.
55
+ :param default: Default answer.
23
56
"""
24
57
def print_question ():
25
58
answers_str = (' [%s]' % (',' .join (answers )) if answers else '' )
@@ -84,7 +117,10 @@ def read_answer():
84
117
85
118
def choice (self , question , options , allow_random = False , default = None ):
86
119
"""
87
- `options`: (name, value) list
120
+ :param options: List of (name, value) tuples.
121
+ :type options: list
122
+ :param allow_random: If ``True``, the default option becomes 'choose random'.
123
+ :type allow_random: bool
88
124
"""
89
125
if len (options ) == 0 :
90
126
raise NoInput ('No options given.' )
@@ -126,7 +162,8 @@ def choice(self, question, options, allow_random=False, default=None):
126
162
127
163
def confirm (self , question , default = None ):
128
164
"""
129
- Print this yes/no question, and return True when the user answers 'yes'.
165
+ Print this yes/no question, and return ``True`` when the user answers
166
+ 'Yes'.
130
167
"""
131
168
answer = 'invalid'
132
169
@@ -140,72 +177,85 @@ def confirm(self, question, default=None):
140
177
return answer in ('yes' , 'y' )
141
178
142
179
#
143
- # Service selector
180
+ # Node selector
144
181
#
145
182
146
- def select_service (self , root_service , prompt = 'Select service ' , filter = None ):
183
+ def select_node (self , root_node , prompt = 'Select a node ' , filter = None ):
147
184
"""
148
- Show autocompletion for service selection.
185
+ Show autocompletion for node selection.
149
186
"""
150
187
from deployer .cli import ExitCLILoop , Handler , HandlerType , CLInterface
151
188
152
- class ServiceHandler (Handler ):
153
- def __init__ (self , service ):
154
- self .service = service
189
+ class NodeHandler (Handler ):
190
+ def __init__ (self , node ):
191
+ self .node = node
155
192
156
193
@property
157
194
def is_leaf (self ):
158
- return not filter or filter (self .service )
195
+ return not filter or filter (self .node )
159
196
160
197
@property
161
198
def handler_type (self ):
162
- class ServiceType (HandlerType ):
163
- color = self .service .get_group ().color
164
- return ServiceType ()
199
+ class NodeType (HandlerType ):
200
+ color = self .node .get_group ().color
201
+ return NodeType ()
165
202
166
203
def complete_subhandlers (self , part ):
167
- for name , subservice in self .service . get_subservices ():
204
+ for name , subnode in self .node . get_subnodes ():
168
205
if name .startswith (part ):
169
- yield name , ServiceHandler ( subservice )
206
+ yield name , NodeHandler ( subnode )
170
207
171
208
def get_subhandler (self , name ):
172
- if self .service . has_subservice (name ):
173
- subservice = self .service . get_subservice (name )
174
- return ServiceHandler ( subservice )
209
+ if self .node . has_subnode (name ):
210
+ subnode = self .node . get_subnode (name )
211
+ return NodeHandler ( subnode )
175
212
176
213
def __call__ (self , context ):
177
- raise ExitCLILoop (self .service )
214
+ raise ExitCLILoop (self .node )
178
215
179
- root_handler = ServiceHandler ( root_service )
216
+ root_handler = NodeHandler ( root_node )
180
217
181
218
class Shell (CLInterface ):
182
219
@property
183
220
def prompt (self ):
184
221
return colored ('\n %s > ' % prompt , 'cyan' )
185
222
186
- not_found_message = 'Service not found...'
187
- not_a_leaf_message = 'Not a valid service ...'
223
+ not_found_message = 'Node not found...'
224
+ not_a_leaf_message = 'Not a valid node ...'
188
225
189
- service_result = Shell (self ._pty , root_handler ).cmdloop ()
226
+ node_result = Shell (self ._pty , root_handler ).cmdloop ()
190
227
191
- if not service_result :
228
+ if not node_result :
192
229
raise NoInput
193
230
194
- return select_service_isolation ( service_result )
231
+ return select_node_isolation ( node_result )
195
232
196
- def select_service_isolation (self , service ):
233
+ def select_node_isolation (self , node ):
197
234
"""
198
235
Ask for a host, from a list of hosts.
199
236
"""
200
- if service ._is_isolated :
201
- return service
202
- else :
203
- options = [ (i .name , i .service ) for i in service .get_isolations () ]
237
+ from deployer .inspection import Inspector
238
+ from deployer .node import IsolationIdentifierType
239
+
240
+ # List isolations first. (This is a list of index/node tuples.)
241
+ options = [
242
+ (' ' .join (index ), node ) for index , node in
243
+ Inspector (node ).iter_isolations (identifier_type = IsolationIdentifierType .HOSTS_SLUG )
244
+ ]
245
+
246
+ if len (options ) > 1 :
204
247
return self .choice ('Choose a host' , options , allow_random = True )
248
+ else :
249
+ return options [0 ][1 ]
205
250
206
251
def lesspipe (self , line_iterator ):
207
252
"""
208
- Paginator for output.
253
+ Paginator for output. This will print one page at a time. When the user
254
+ presses a key, the next page is printed. ``Ctrl-c`` or ``q`` will quit
255
+ the paginator.
256
+
257
+ :param line_iterator: A generator function that yields lines (without
258
+ trailing newline)
209
259
"""
210
260
height = self ._pty .get_size ()[0 ] - 1
211
261
@@ -242,8 +292,8 @@ def lesspipe(self, line_iterator):
242
292
243
293
def in_columns (self , item_iterator , margin_left = 0 ):
244
294
"""
245
- ` item_iterator' should be an iterable, which yields either
246
- basestring , or (colored_item, length)
295
+ :param item_iterator: An iterable, which yields either ``basestring``
296
+ instances , or (colored_item, length) tuples.
247
297
"""
248
298
# Helper functions for extracting items from the iterator
249
299
def get_length (item ):
0 commit comments