Browse Source

Merge branch 'test'

Ian Adam Naval 6 years ago
parent
commit
26529fe5a1
14 changed files with 147 additions and 26 deletions
  1. 1
    0
      .gitignore
  2. 44
    0
      README.md
  3. 11
    0
      psh/__init__.py
  4. 8
    6
      psh/commands.py
  5. 4
    5
      psh/console.py
  6. 7
    8
      psh/example_cmd.py
  7. 1
    1
      psh/formatters.py
  8. 5
    3
      psh/raw_commands.py
  9. 5
    3
      psh/run.py
  10. 0
    0
      psh/tree.py
  11. 1
    0
      requirements.txt
  12. 20
    0
      setup.py
  13. 19
    0
      test/test_example_cmd.py
  14. 21
    0
      test/utils.py

+ 1
- 0
.gitignore View File

@@ -1,2 +1,3 @@
1 1
 __pycache__
2 2
 *.pyc
3
+*.egg-info

+ 44
- 0
README.md View File

@@ -0,0 +1,44 @@
1
+psh
2
+===
3
+
4
+Augmented Unix Userland shell inspired by Windows PowerShell, written in Python.
5
+
6
+Requirements
7
+------------
8
+
9
+* Python 3+
10
+* pip
11
+
12
+Installing
13
+----------
14
+
15
+Preferably, you would use a separate virtual env
16
+
17
+```
18
+pip install -r requirements.txt
19
+pip install -e .  # installs the 'psh' package in editable mode
20
+```
21
+
22
+Running
23
+-------
24
+
25
+From Python shell:
26
+
27
+```
28
+from psh.run import main
29
+main()
30
+```
31
+
32
+From Unix shell:
33
+```
34
+python -m psh.run
35
+```
36
+
37
+Testing
38
+-------
39
+
40
+From Unix shell:
41
+
42
+```
43
+py.test
44
+```

+ 11
- 0
psh/__init__.py View File

@@ -0,0 +1,11 @@
1
+from psh.commands import registered_cmds
2
+
3
+# Import the exported commands
4
+from psh.example_cmd import *
5
+
6
+# Instantiate the registered commands
7
+for name, cls in registered_cmds.items():
8
+	globals()[name] = cls()
9
+
10
+# Only export the names of registered commands
11
+__all__ = registered_cmds.keys()

commands.py → psh/commands.py View File

@@ -38,9 +38,11 @@ class BaseCommand(object):
38 38
         return cmd
39 39
 
40 40
 
41
-registered_cmds = []
42
-
43
-def register_cmd(cls):
44
-    """Decorator for putting all of the commands in one nice place."""
45
-    registered_cmds.append(cls.__name__)
46
-    return cls
41
+registered_cmds = {}
42
+
43
+def register_cmd(name):
44
+    def decorator(cls):
45
+        """Decorator for putting all of the commands in one nice place."""
46
+        registered_cmds[name] = cls
47
+        return cls
48
+    return decorator

console.py → psh/console.py View File

@@ -2,10 +2,8 @@ import atexit
2 2
 import code
3 3
 import os
4 4
 import readline
5
-import shlex
6 5
 
7
-from commands import registered_cmds
8
-import example_cmd
6
+from psh.commands import registered_cmds
9 7
 
10 8
 DEFAULT_HISTORY_FILE = "~/.psh_history"
11 9
 
@@ -24,9 +22,10 @@ def parse_cmd(potential_cmd):
24 22
     if args:
25 23
         args = args[1:]
26 24
     if cmd_name not in registered_cmds:
27
-        return "RawCommand({})".format(shlex.split(potential_cmd))
25
+        return "RawCommand('{}')".format(potential_cmd)
28 26
     else:
29
-        return "{0}({1})".format(cmd_name,str(args))
27
+        cls = registered_cmds[cmd_name].__name__
28
+        return "{0}({1})".format(cls, str(args))
30 29
 
31 30
 
32 31
 def parse_cmds(raw_input_line):

example_cmd.py → psh/example_cmd.py View File

@@ -1,8 +1,8 @@
1
-from commands import BaseCommand, register_cmd
1
+from psh.commands import BaseCommand, register_cmd
2 2
 
3 3
 
4
-@register_cmd
5
-class example_cmd(BaseCommand):
4
+@register_cmd("example")
5
+class Example(BaseCommand):
6 6
     """Simple command that just returns 'example' and 'command'. Does
7 7
     nothing at all with the input."""
8 8
 
@@ -13,13 +13,13 @@ class example_cmd(BaseCommand):
13 13
         return output_generator
14 14
 
15 15
 
16
-@register_cmd
17
-class echo(BaseCommand):
16
+@register_cmd("echo")
17
+class Echo(BaseCommand):
18 18
     """Echoes anything from the command line arguments as well as input
19 19
     from the previous command."""
20 20
 
21
-    def __init__(self, args):
22
-        super(echo, self).__init__()
21
+    def __init__(self, args=[]):
22
+        super(Echo, self).__init__()
23 23
         self.args = args
24 24
 
25 25
     def call(self,*args,**kwargs):
@@ -30,4 +30,3 @@ class echo(BaseCommand):
30 30
             for line in input_generator:
31 31
                 yield line
32 32
         return output_generator
33
-

formatters.py → psh/formatters.py View File

@@ -1,4 +1,4 @@
1
-from commands import BaseCommand
1
+from psh.commands import BaseCommand
2 2
 
3 3
 
4 4
 class Printer(BaseCommand):

raw_commands.py → psh/raw_commands.py View File

@@ -1,5 +1,7 @@
1
-from formatters import Printer
2
-from commands import BaseCommand
1
+import shlex
2
+
3
+from psh.formatters import Printer
4
+from psh.commands import BaseCommand
3 5
 
4 6
 
5 7
 class RawCommand(BaseCommand):
@@ -15,7 +17,7 @@ class RawCommand(BaseCommand):
15 17
         input_generator = self.get_input_generator()
16 18
         import subprocess
17 19
         try:
18
-            p = subprocess.Popen(self.cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
20
+            p = subprocess.Popen(shlex.split(self.cmd), stdin=subprocess.PIPE, stdout=subprocess.PIPE)
19 21
             def make_output_generator():
20 22
                 input_str = b""
21 23
                 for line in input_generator:

main.py → psh/run.py View File

@@ -1,8 +1,10 @@
1 1
 import os
2 2
 import os.path
3 3
 
4
-from formatters import *
5
-from raw_commands import RawCommand
4
+from psh.formatters import *
5
+
6
+from psh.example_cmd import Echo, Example
7
+from psh.raw_commands import RawCommand
6 8
 
7 9
 # Load all of the commands in the path into the global namespace as raw
8 10
 # commands.
@@ -15,7 +17,7 @@ for path in os.environ['PATH'].split(':'):
15 17
 
16 18
 
17 19
 def main():
18
-    from console import HistoryConsole
20
+    from psh.console import HistoryConsole
19 21
     console = HistoryConsole(globals())
20 22
     console.interact("Augmented Unix Userland")
21 23
 

tree.py → psh/tree.py View File


+ 1
- 0
requirements.txt View File

@@ -0,0 +1 @@
1
+pytest

+ 20
- 0
setup.py View File

@@ -0,0 +1,20 @@
1
+import os
2
+from setuptools import setup
3
+
4
+# Utility function to read the README file.
5
+# Used for the long_description.  It's nice, because now 1) we have a top level
6
+# README file and 2) it's easier to type in the README file than to put a raw
7
+# string in below ...
8
+def read(fname):
9
+    return open(os.path.join(os.path.dirname(__file__), fname)).read()
10
+
11
+setup(
12
+    name = "psh",
13
+    version = "0.0.1",
14
+    author = "WPI Augmented Unix Userland MQP",
15
+    author_email = "jsh@wpi.edu",
16
+    description = ("Simple Unix shell inspired by PowerShell"),
17
+    license = "MIT",
18
+    packages=['psh'],
19
+    long_description=read('README.md'),
20
+)

+ 19
- 0
test/test_example_cmd.py View File

@@ -0,0 +1,19 @@
1
+import pytest
2
+
3
+from psh import echo, example
4
+from utils import TestFormatter
5
+
6
+
7
+@pytest.fixture
8
+def test_formatter():
9
+    return TestFormatter()
10
+
11
+
12
+def test_example_cmd_should_return_two_things(test_formatter):
13
+    example.chain(test_formatter).call()
14
+    assert "examplecommand" == test_formatter.get_data()
15
+
16
+
17
+def test_echo_should_echo(test_formatter):
18
+    example.chain(echo).chain(test_formatter).call()
19
+    assert "examplecommand" == test_formatter.get_data()

+ 21
- 0
test/utils.py View File

@@ -0,0 +1,21 @@
1
+from psh.commands import BaseCommand
2
+
3
+from io import StringIO
4
+
5
+class TestFormatter(BaseCommand):
6
+    """Formatter useful for tests. Instead of printing to stdout, it
7
+    stores any output inside a stringio buffer. This can be retrieved
8
+    with the get_data method."""
9
+
10
+    def __init__(self, *args, **kwargs):
11
+        super(TestFormatter, self).__init__(*args, **kwargs)
12
+        self.buffer = StringIO()
13
+
14
+    def call(self):
15
+        input_generator = self.get_input_generator()
16
+        for line in input_generator:
17
+            self.buffer.write(line.decode('utf-8'))
18
+        return None
19
+
20
+    def get_data(self):
21
+        return self.buffer.getvalue()

Loading…
Cancel
Save