Skip to content

Commit

Permalink
Merge pull request #31 from TheFriendlyCoder/shell_cmds
Browse files Browse the repository at this point in the history
Fixes #18 Added support for shell commands
  • Loading branch information
TheFriendlyCoder committed Apr 28, 2018
2 parents 9c68b6e + 91fbaba commit f2e28b8
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 4 deletions.
22 changes: 21 additions & 1 deletion src/friendlyshell/base_shell.py
Expand Up @@ -2,6 +2,7 @@
import os
import sys
import inspect
import subprocess
import pyparsing as pp
from six.moves import input
from friendlyshell.command_parsers import default_line_parser
Expand Down Expand Up @@ -70,7 +71,6 @@ def _get_input(self):
:returns: the input line retrieved from the source
:rtype: :class:`str`
"""
line = None
try:
if self.input_stream:
line = self.input_stream.readline()
Expand Down Expand Up @@ -142,6 +142,22 @@ def _execute_command(self, func, parser):
# garbage to the user
self.debug(err, exc_info=True)

def _run_shell_command(self, cmd):
"""Executes a shell command within the Friendly Shell environment
:param str cmd: Shell command to execute
"""
self.debug("Running shell command %s", cmd)
try:
output = subprocess.check_output(
cmd,
shell=True,
stderr=subprocess.STDOUT)
self.info(output)
except subprocess.CalledProcessError as err:
self.info("Failed to run command %s: %s", err.cmd, err.returncode)
self.info(err.output)

def run(self, *_args, **kwargs):
"""Main entry point function that launches our command line interpreter
Expand All @@ -163,6 +179,10 @@ def run(self, *_args, **kwargs):
if not line:
continue

if line[0] == "!":
self._run_shell_command(line[1:])
continue

parser = self._parse_line(line)
if parser is None:
continue
Expand Down
8 changes: 6 additions & 2 deletions src/friendlyshell/command_complete_mixin.py
Expand Up @@ -290,6 +290,7 @@ def _complete_callback(self, token, index):
Returns None if there are no matches for the given token
"""
try:
line = readline.get_line_buffer()
# ------------------------- DEBUG OUTPUT ---------------------------
# NOTE: The begidx and endidx parameters specify the start and end+1
# location of the sub-string
Expand All @@ -303,7 +304,7 @@ def _complete_callback(self, token, index):
self.debug('\t\tSelected token "%s"', token)
self.debug('\t\tMatch to return "%s"', index)
# All text currently entered at the prompt, less the prompt itself
self.debug('\t\tline "%s"', readline.get_line_buffer())
self.debug('\t\tline "%s"', line)
# represents the offset from the start of the string to the first
# character in the token to process
self.debug('\t\tBeginning index "%s"', readline.get_begidx())
Expand All @@ -315,7 +316,10 @@ def _complete_callback(self, token, index):
# this index would be: len(line) + 1
self.debug('\t\tEnding index "%s"', readline.get_endidx())
# ------------------------------------------------------------------

if readline.get_line_buffer()[0] == "!":
self.debug(
"Processing subcommand '%s'. Skipping command expansion.",
line)
if index != 0:
if index >= len(self._latest_matches):
self.debug('Completed auto completion routine.')
Expand Down
19 changes: 18 additions & 1 deletion src/friendlyshell/shell_help_mixin.py
Expand Up @@ -51,8 +51,23 @@ def _list_commands(self):
else:
command_list['Extended Help'].append('N/A')

self.info("COMMANDS")
self.info(tabulate.tabulate(command_list, headers="keys"))

def _list_operators(self):
"""Displays a list of built-in operators supported by Friendly Shell"""
operator_list = {
'Operator': ["!"],
'Description': [
"Redirects command to the native console"
],
'Examples': [
"'!dir /ah', '!ls -alh'"
]
}
self.info("OPERATORS")
self.info(tabulate.tabulate(operator_list, headers="keys"))

def do_help(self, arg=None):
"""Online help generation (this command)
Expand All @@ -62,8 +77,10 @@ def do_help(self, arg=None):
"""
# no command given, show available commands
if arg is None:
self.debug("Showing help for available commands...")
self.debug("Showing default help output...")
self._list_commands()
self.info("\n")
self._list_operators()
return

# Sanity check: make sure we're asking for help for a command
Expand Down
43 changes: 43 additions & 0 deletions tests/test_base_shell.py
@@ -1,5 +1,7 @@
from __future__ import unicode_literals
import logging
import sys
import os
from friendlyshell.base_shell import BaseShell
from friendlyshell.basic_logger_mixin import BasicLoggerMixin
from mock import patch
Expand Down Expand Up @@ -310,5 +312,46 @@ def do_something(self, my_param1, my_param2):
assert msg in caplog.text


def test_shell_command(caplog):
caplog.set_level(logging.INFO)
class test_class(BasicLoggerMixin, BaseShell):
pass

obj = test_class()
if sys.platform.startswith("win"):
test_cmd = "dir /a"
else:
test_cmd = "ls -a"

in_stream = StringIO("""!{0}
exit""".format(test_cmd))

obj.run(input_stream=in_stream)

for cur_item in os.listdir("."):
assert cur_item in caplog.text


def test_shell_command_not_found(caplog):
caplog.set_level(logging.INFO)
expected_text = "Hello World"
class test_class(BasicLoggerMixin, BaseShell):
def do_something(self):
self.info(expected_text)

obj = test_class()
expected_command = "fubarasdf1234"
in_stream = StringIO("""!{0}
something
exit""".format(expected_command))

obj.run(input_stream=in_stream)

# Make sure the second command in the sequence rance
assert expected_text in caplog.text
if sys.platform.startswith("win"):
assert "not recognized" in caplog.text
assert expected_command in caplog.text

if __name__ == "__main__":
pytest.main([__file__, "-v", "-s"])
1 change: 1 addition & 0 deletions tests/test_help_mixin.py
Expand Up @@ -21,6 +21,7 @@ def do_something(self):
assert 'exit' in caplog.text
assert 'help' in caplog.text
assert 'something' in caplog.text
assert '!' in caplog.text
assert obj.do_something.__doc__ in caplog.text


Expand Down

0 comments on commit f2e28b8

Please sign in to comment.