Commit 7341c226 authored by Stavros Sachtouris's avatar Stavros Sachtouris
Browse files

Merge branch 'features/history' into develop

parents c5a9cfae ee9f8245
......@@ -87,6 +87,7 @@ Command user history, as stored in ~/.kamaki.history
* all show user history
* clean clean up history
* load Run previously executed command(s)
server commands
......
......@@ -614,6 +614,57 @@ Session history is only available in interactive shell mode. Users can iterate t
Permanent history is implemented as a command group and is common to both the one-command and shell interfaces. In specific, every syntactically correct command is appended in a history file (configured as *history.file* in settings, see `setup section <setup.html>`_ for details). Commands executed in one-command mode are mixed with the ones run in kamaki shell (also see :ref:`using-history-ref` section on this guide).
Scripting
^^^^^^^^^
Since version 6.2, the history-load feature allows the sequential execution of previously run kamaki commands in kamaki shell.
The following kamaki sequence copies and downloads a file from mycontainer1, uploads it to mycontainer2, then undo the proccess and repeats it with history-load
.. code-block:: console
:emphasize-lines: 1,12,19,32
* Download mycontainer1:myfile and upload it to mycontainer2:myfile
[kamaki]: store
[store]: copy mycontainer1:somefile mycontainer1:myfile
[store]: download mycontainer1:myfile mylocalfile
Download completed
[store]: upload mylocalfile mycontainer2:myfile
Upload completed
* undo the process *
[store]: !rm mylocalfile
[store]: delete mycontainer1:myfile
[store]: delete mycontainer2:myfile
* check history entries *
[store]: exit
[kamaki]: history
[history]: show
1. store
2. store copy mycontainer1:somefile mycontainer1:myfile
3. store download mycontainer1:myfile mylocalfile
4. store upload mylocalfile mycontainer2:myfile
5. history
6. history show
*repeat the process *
[history]: load 2-4
store copy mycontainer1:somefile mycontainer1:myfile
store download mycontainer1:myfile mylocalfile
Download completed
store upload mylocalfile mycontainer2:myfile
Upload completed
The above strategy is still very primitive. Users are advised to take advantage of their os shell scripting capabilities and combine them with kamaki one-command for powerful scripting. Still, the history-load functionality might prove handy for kamaki shell users.
Tab completion
^^^^^^^^^^^^^^
......@@ -652,12 +703,12 @@ Kamaki shell features the ability to execute OS-shell commands from any context.
-rw-rw-r-- 1 saxtouri saxtouri 8063 Jun 28 14:48 logo-copy.png
Kamaki shell commits command strings to the outside shell and prints the results, without interacting with it. After a command is finished, kamaki shell returns to its initial state, which involves the current directory, as show in example 4.7.2 .
Kamaki shell commits command strings to the outside shell and prints the results, without interacting with it. After a command is finished, kamaki shell returns to its initial state, which involves the current directory, as show in example 4.8.2 .
.. code-block:: console
:emphasize-lines: 1
Example 4.7.2: Attempt (and fail) to change working directory
Example 4.8.2: Attempt (and fail) to change working directory
[kamaki]:!pwd
......
......@@ -40,6 +40,7 @@ from kamaki.cli.argument import ArgumentParseManager
from kamaki.cli.utils import print_dict, split_input, print_items
from kamaki.cli.history import History
from kamaki.cli.errors import CLIError
from kamaki.clients import ClientError
def _init_shell(exe_string, parser):
......@@ -164,7 +165,10 @@ class Shell(Cmd):
# exec command or change context
if subcmd.is_command: # exec command
cls = subcmd.get_class()
instance = cls(dict(cmd_parser.arguments))
if subcmd.path == 'history_load':
instance = cls(dict(cmd_parser.arguments), self.cmd_tree)
else:
instance = cls(dict(cmd_parser.arguments))
cmd_parser.update_arguments(instance.arguments)
instance.arguments.pop('config')
#cmd_parser = ArgumentParseManager(subcmd.path,
......@@ -180,11 +184,12 @@ class Shell(Cmd):
for name, arg in instance.arguments.items():
arg.value = getattr(cmd_parser.parsed, name, arg.default)
try:
_exec_cmd(instance,
cmd_parser.unparsed,
cmd_parser.parser.print_help)
except CLIError as err:
except (ClientError, CLIError) as err:
_print_error_message(err)
elif ('-h' in cmd_args or '--help' in cmd_args) \
or len(cmd_args): # print options
......
......@@ -155,6 +155,23 @@ class CommandTree(object):
if description is not None:
cmd.help = description
def find_best_match(self, terms):
"""Find a command that best matches a given list of terms
:param terms: (list of str) match them against paths in cmd_tree
:returns: (Command, list) the matching command, the remaining terms
"""
path = []
for term in terms:
check_path = path + [term]
if '_'.join(check_path) not in self._all_commands:
break
path = check_path
if path:
return (self._all_commands['_'.join(path)], terms[len(path):])
return (None, terms)
def add_tree(self, new_tree):
tname = new_tree.name
tdesc = new_tree.description
......
......@@ -35,9 +35,14 @@
from kamaki.cli.command_tree import CommandTree
from kamaki.cli.argument import IntArgument, ValueArgument
from kamaki.cli.argument import ArgumentParseManager
from kamaki.cli.history import History
from kamaki.cli import command
from kamaki.cli.commands import _command_init
from kamaki.cli import _exec_cmd, _print_error_message
from kamaki.cli.errors import CLIError, CLISyntaxError, raiseCLIError
from kamaki.cli.utils import split_input
from kamaki.clients import ClientError
history_cmds = CommandTree('history', 'Command history')
......@@ -74,3 +79,61 @@ class history_clean(_init_history):
def main(self):
super(self.__class__, self).main()
self.history.clean()
@command(history_cmds)
class history_load(_init_history):
"""Run previously executed command(s)"""
_cmd_tree = None
def __init__(self, arguments={}, cmd_tree=None):
super(self.__class__, self).__init__(arguments)
self._cmd_tree = cmd_tree
def _run_from_line(self, line):
terms = split_input(line)
cmd, args = self._cmd_tree.find_best_match(terms)
if not cmd.is_command:
return
try:
instance = cmd.get_class()(self.arguments)
instance.config = self.config
prs = ArgumentParseManager(cmd.path.split(),
dict(instance.arguments))
prs.syntax = '%s %s' % (cmd.path.replace('_', ' '),
cmd.get_class().syntax)
prs.parse(args)
_exec_cmd(instance, prs.unparsed, prs.parser.print_help)
except (CLIError, ClientError) as err:
_print_error_message(err)
except Exception as e:
print('Execution of [ %s ] failed' % line)
print('\t%s' % e)
def _get_cmd_ids(self, cmd_ids):
if not cmd_ids:
raise CLISyntaxError('Usage: <id1> [id2] ... [id3-id4] ...',
details=' where id* are for commands in history')
cmd_id_list = []
for cmd_str in cmd_ids:
num1, sep, num2 = cmd_str.partition('-')
try:
if sep:
for i in range(int(num1), int(num2) + 1):
cmd_id_list.append(i)
else:
cmd_id_list.append(int(cmd_str))
except ValueError:
raiseCLIError('Invalid history id %s' % cmd_str)
return cmd_id_list
def main(self, *command_ids):
super(self.__class__, self).main()
cmd_list = self._get_cmd_ids(command_ids)
for cmd_id in cmd_list:
r = self.history.retrieve(cmd_id)
print(r[:-1])
if self._cmd_tree:
r = r[len('kamaki '):-1] if r.startswith('kamaki ') else r[:-1]
self._run_from_line(r)
......@@ -71,3 +71,16 @@ class History(object):
def clean(self):
f = open(self.filepath, 'w')
f.close()
def retrieve(self, cmd_id):
"""
:param cmd_id: (int) the id of the command to retrieve
:returns: (str) the stored command record without the id
"""
cmd_id = int(cmd_id)
with open(self.filepath) as f:
try:
return f.readlines()[cmd_id - 1]
except IndexError:
return None
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment