Undo server restructs, keep the big fixes
[kamaki] / kamaki / cli / commands / history.py
1 #!/usr/bin/env python
2
3 # Copyright 2012-2013 GRNET S.A. All rights reserved.
4 #
5 # Redistribution and use in source and binary forms, with or
6 # without modification, are permitted provided that the following
7 # conditions are met:
8 #
9 #   1. Redistributions of source code must retain the above
10 #      copyright notice, this list of conditions and the following
11 #      disclaimer.
12 #
13 #   2. Redistributions in binary form must reproduce the above
14 #      copyright notice, this list of conditions and the following
15 #      disclaimer in the documentation and/or other materials
16 #      provided with the distribution.
17 #
18 # THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
19 # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
22 # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
25 # USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
26 # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
28 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 # POSSIBILITY OF SUCH DAMAGE.
30 #
31 # The views and conclusions contained in the software and
32 # documentation are those of the authors and should not be
33 # interpreted as representing official policies, either expressed
34 # or implied, of GRNET S.A.
35
36 from kamaki.cli.command_tree import CommandTree
37 from kamaki.cli.argument import IntArgument, ValueArgument
38 from kamaki.cli.argument import ArgumentParseManager
39 from kamaki.cli.history import History
40 from kamaki.cli import command
41 from kamaki.cli.commands import _command_init, errors
42 from kamaki.cli import exec_cmd, print_error_message
43 from kamaki.cli.errors import CLIError, raiseCLIError
44 from kamaki.cli.utils import split_input
45 from kamaki.clients import ClientError
46
47
48 history_cmds = CommandTree('history', 'Kamaki command history')
49 _commands = [history_cmds]
50
51
52 def _get_num_list(num_str):
53     if num_str.startswith('-'):
54         num1, sep, num2 = num_str[1:].partition('-')
55         num1 = '-%s' % num1
56     else:
57         num1, sep, num2 = num_str.partition('-')
58     (num1, num2) = (num1.strip(), num2.strip())
59     try:
60         num1 = (-int(num1[1:])) if num1.startswith('-') else int(num1)
61     except ValueError as e:
62         raiseCLIError(e, 'Invalid id %s' % num1)
63     if sep:
64         try:
65             num2 = (-int(num2[1:])) if num2.startswith('-') else int(num2)
66             num2 += 1 if num2 > 0 else 0
67         except ValueError as e:
68             raiseCLIError(e, 'Invalid id %s' % num2)
69     else:
70         num2 = (1 + num1) if num1 else 0
71     return [i for i in range(num1, num2)]
72
73
74 class _init_history(_command_init):
75     @errors.generic.all
76     @errors.history.init
77     def _run(self):
78         self.history = History(self.config.get('global', 'history_file'))
79
80     def main(self):
81         self._run()
82
83
84 @command(history_cmds)
85 class history_show(_init_history):
86     """Show intersession command history
87     ---
88     - With no parameters : pick all commands in history records
89     - With:
90     .   1.  <order-id> : pick the <order-id>th command
91     .   2.  <order-id-1>-<order-id-2> : pick all commands ordered in the range
92     .       [<order-id-1> - <order-id-2>]
93     .   - the above can be mixed and repeated freely, separated by spaces
94     .       e.g., pick 2 4-7 -3
95     .   - Use negative integers to count from the end of the list, e.g.,:
96     .       -2 means : the command before the last one
97     .       -2-5 means : last 2 commands + the first 5
98     .       -5--2 means : the last 5 commands except the last 2
99     """
100
101     arguments = dict(
102         limit=IntArgument(
103             'number of lines to show',
104             ('-n', '--numner'),
105             default=0),
106         match=ValueArgument('show lines that match given terms', '--match')
107     )
108
109     @errors.generic.all
110     def _run(self, *cmd_ids):
111         ret = self.history.get(match_terms=self['match'], limit=self['limit'])
112
113         if not cmd_ids:
114             self.print_list(ret)
115             return
116
117         num_list = []
118         for num_str in cmd_ids:
119             num_list += _get_num_list(num_str)
120
121         for cmd_id in num_list:
122             try:
123                 cur_id = int(cmd_id)
124                 if cur_id:
125                     self.writeln(ret[cur_id - (1 if cur_id > 0 else 0)][:-1])
126             except IndexError as e2:
127                 raiseCLIError(e2, 'Command id out of 1-%s range' % len(ret))
128
129     def main(self, *cmd_ids):
130         super(self.__class__, self)._run()
131         self._run(*cmd_ids)
132
133
134 @command(history_cmds)
135 class history_clean(_init_history):
136     """Clean up history (permanent)"""
137
138     @errors.generic.all
139     def _run(self):
140         self.history.clean()
141
142     def main(self):
143         super(self.__class__, self)._run()
144         self._run()
145
146
147 @command(history_cmds)
148 class history_run(_init_history):
149     """Run previously executed command(s)
150     Use with:
151     .   1.  <order-id> : pick the <order-id>th command
152     .   2.  <order-id-1>-<order-id-2> : pick all commands ordered in the range
153     .       [<order-id-1> - <order-id-2>]
154     .   - Use negative integers to count from the end of the list, e.g.,:
155     .       -2 means : the command before the last one
156     .       -2-5 means : last 2 commands + the first 5
157     .       -5--2 mean
158     .   - to find order ids for commands try   /history show.
159     """
160
161     _cmd_tree = None
162
163     def __init__(self, arguments={}, auth_base=None, cmd_tree=None):
164         super(self.__class__, self).__init__(arguments, auth_base=auth_base)
165         self._cmd_tree = cmd_tree
166
167     @errors.generic.all
168     def _run_from_line(self, line):
169         terms = split_input(line)
170         cmd, args = self._cmd_tree.find_best_match(terms)
171         if cmd.is_command:
172             try:
173                 instance = cmd.cmd_class(
174                     self.arguments, auth_base=getattr(self, 'auth_base', None))
175                 instance.config = self.config
176                 prs = ArgumentParseManager(
177                     cmd.path.split(), dict(instance.arguments))
178                 prs.syntax = '%s %s' % (
179                     cmd.path.replace('_', ' '), cmd.cmd_class.syntax)
180                 prs.parse(args)
181                 exec_cmd(instance, prs.unparsed, prs.parser.print_help)
182             except (CLIError, ClientError) as err:
183                 print_error_message(err, self._err)
184             except Exception as e:
185                 self.error('Execution of [ %s ] failed\n\t%s' % (line, e))
186
187     @errors.generic.all
188     @errors.history._get_cmd_ids
189     def _get_cmd_ids(self, cmd_ids):
190         cmd_id_list = []
191         for cmd_str in cmd_ids:
192             cmd_id_list += _get_num_list(cmd_str)
193         return cmd_id_list
194
195     @errors.generic.all
196     def _run(self, *command_ids):
197         cmd_list = self._get_cmd_ids(command_ids)
198         for cmd_id in cmd_list:
199             r = self.history.retrieve(cmd_id)
200             try:
201                 self.writeln('< %s >' % r[:-1])
202             except (TypeError, KeyError):
203                 continue
204             if self._cmd_tree:
205                 r = r[len('kamaki '):-1] if r.startswith('kamaki ') else r[:-1]
206                 self._run_from_line(r)
207
208     def main(self, *command_ids):
209         super(self.__class__, self)._run()
210         self._run(*command_ids)