From 0d82cea312fd83db7c0bba204c17c60cb6b359a7 Mon Sep 17 00:00:00 2001
From: Seblu <sebastien.luttringer@smartjog.com>
Date: Wed, 22 Dec 2010 13:39:48 +0100
Subject: [PATCH] Rome was not built in one day

---
 .gitignore        |   2 +
 CHANGELOG         |   2 +
 Makefile          |   8 ++++
 README            |   7 +++
 arch/PKGBUILD     |   0
 bin/cc-cli        |  51 +++++++++++---------
 cccli/__init__.py |   3 +-
 cccli/cli.py      | 117 ++++++++++++++++++++++++++++++++++++----------
 cccli/clierror.py |   3 ++
 cccli/command.py  |  54 +++++++++++++++++++++
 cccli/parser.py   |   1 +
 cccli/printer.py  |  40 +++++++++-------
 debian/control    |   4 +-
 13 files changed, 226 insertions(+), 66 deletions(-)
 create mode 100644 .gitignore
 create mode 100644 Makefile
 create mode 100644 arch/PKGBUILD
 create mode 100644 cccli/command.py

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f3d74a9
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+*.pyc
+*~
diff --git a/CHANGELOG b/CHANGELOG
index e69de29..4c0d2bb 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -0,0 +1,2 @@
+v1
+- Initial release
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..ca3965b
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,8 @@
+## Makefile
+
+.PHONY: deb debclean
+
+all:
+
+deb:
+	dpkg-buildpackage
diff --git a/README b/README
index e69de29..245a349 100644
--- a/README
+++ b/README
@@ -0,0 +1,7 @@
+===========
+New release
+===========
+Update version in debian/control
+Update version in debian/changelog
+Update version in setup.py
+Update version in cccli/__init__.py
diff --git a/arch/PKGBUILD b/arch/PKGBUILD
new file mode 100644
index 0000000..e69de29
diff --git a/bin/cc-cli b/bin/cc-cli
index 2d212cd..dbeb2aa 100755
--- a/bin/cc-cli
+++ b/bin/cc-cli
@@ -40,48 +40,55 @@ try:
         settings["debug"] = "True"
 
     # Parse line argument
-    oparser = optparse.OptionParser(usage="usage: %prog [options] [server[:port]]",
+    oparser = optparse.OptionParser(usage="usage: %prog [options] [commands]",
                                     version=cccli.version)
     oparser.add_option("-d", "--debug",
                        action="store_true",
                        dest="debug",
-                       default=False,
+                       default="",
                        help="debug mode")
     oparser.add_option("-l", "--login",
                        action="store",
                        dest="login",
                        default="",
                        help="server login")
+    oparser.add_option("-s", "--server",
+                       action="store",
+                       dest="server",
+                       default="",
+                       help="server address")
+    oparser.add_option("-p", "--port",
+                       action="store",
+                       dest="port",
+                       default="1984",
+                       help="server port")
+
     (options, args) = oparser.parse_args()
 
+    # options handling
+    for i in ("debug", "login", "server", "port"):
+        if hasattr(options, i):
+            o = getattr(options, i)
+            if o != "":
+                settings[i] = o
+
     # show usage if no default conf and no server given
-    if len(settings) == 0 and len(args) == 0:
+    if "login" not in settings or "server" not in settings:
         oparser.print_help()
         sys.exit(1)
 
-    # debug option handling
-    if options.debug or "debug" in settings:
-        cccli.debug = True
-
-    # args handling
-    if len(args) == 1:
-        sargs = args[0].split(":", 1)
-        if len(sargs) > 1:
-            settings["port"] = sargs[1]
-        if not sargs[0] == "":
-            settings["host"] = sargs[0]
-
-    # remove password to prevent it showing
+    # remove pass to prevent stupid guy
     if "pass" in settings:
         del settings["pass"]
 
     # debug stuff
-    if cccli.debug:
-        printer.err("Debugging on")
-        printer.err("Settings: %s"%settings)
-        printer.err("Alias: %s"%alias)
+    if "debug" in settings:
+        cccli.debug = bool(settings["debug"])
     else:
         warnings.filterwarnings("ignore")
+    printer.debug("Debugging on")
+    printer.debug("Settings: %s"%settings)
+    printer.debug("Alias: %s"%alias)
 
     # checking needs
     if settings["server"] == "":
@@ -99,8 +106,8 @@ try:
     settings["pass"] = getpass.getpass("Password: ")
 
     # start cli
-    cli = cli.Cli(settings, alias)
-    cli.run()
+    cli = cli.Cli(settings, alias, args)
+    cli.start()
 except KeyboardInterrupt:
     exit(1)
 except Exception, e:
diff --git a/cccli/__init__.py b/cccli/__init__.py
index cb0cf1b..22fbde2 100644
--- a/cccli/__init__.py
+++ b/cccli/__init__.py
@@ -9,5 +9,6 @@ version = "1~dev"
 debug = False
 
 import printer
-import parser
+import cli
 import clierror
+import command
diff --git a/cccli/cli.py b/cccli/cli.py
index 10f134d..b368dce 100644
--- a/cccli/cli.py
+++ b/cccli/cli.py
@@ -10,38 +10,107 @@ import sys
 import socket
 import ssl
 import threading
+import cccli
+from cccli import printer, command
+from cccli.clierror import *
 from sjrpc.client import SimpleRpcClient
 from sjrpc.utils import RpcHandler, pure, threadless, ConnectionProxy
 
+import re
+import readline
 
 class Cli(object):
-    _settings = {}
-    _alias = {}
 
-    def __init__(self, settings, alias):
+    def __init__(self, settings, alias, args):
         self._settings = settings
         self._alias = alias
+        self._interactive = sys.stderr.isatty() and sys.stdin.isatty()
+        self._prompt = "> "
+        self._commands = args
+        self._rpc = None
 
-    def run(self):
-        # try to connect
-        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-        sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
-        sock = ssl.wrap_socket(sock, certfile=None,
-                               cert_reqs=ssl.CERT_NONE,
-                               ssl_version=ssl.PROTOCOL_TLSv1)
-        sock.connect((self._settings["server"], self._settings["port"]))
-        client = SimpleRpcClient(sock)
-        print "sucks"
-        prox = ConnectionProxy(client)
-        print "sucks1"
-        t = threading.Thread(target=client.run)
-        t.daemon = True
-        t.start()
-        print "sucks2"
-        prox.authentify(self._settings["login"], self._settings["pass"])
-        print "sucks3"
-        # run parsing
-        #cparser = parser.Parser(main, alias)
-        #cparser.parse()
+    def start(self):
+        '''Start a CLI'''
+        # Connecting
+        self._connect()
+        # authentifications
+        self._auth()
+        # run parsing args
+        if len(self._commands) > 0:
+            for c in self._commands:
+                self._parse(c)
+        elif self._interactive:
+            self._interactive_parser()
+        else:
+            self._parser()
 
+    def _connect(self):
+        printer.debug("Connecting...")
+        rpcc = SimpleRpcClient.from_addr(self._settings["server"],
+                                        self._settings["port"],
+                                        enable_ssl=True
+                                        )
+        # FIXME: wait sjrpc v5 with on_disconnect usable
+        rpcc.start(daemonize=True)
+        self._rpc = ConnectionProxy(rpcc)
 
+    def _auth(self):
+        printer.debug("Authenticating...")
+        if self._rpc.authentify(self._settings["login"], self._settings["pass"]):
+            printer.debug("Authenticated.")
+        else:
+            printer.fatal("Autentification failed!")
+
+    def _interactive_parser(self):
+        '''Interactive shell parser'''
+        # init readline
+
+        while True:
+            try:
+                line = raw_input(self._prompt)
+                self._parse_line(line)
+            except BadCommand, e:
+                printer.error("No such command: %s"%e[0])
+            except EOFError:
+                printer.out("")
+                break
+            except SystemExit:
+                break
+            except KeyboardInterrupt:
+                printer.out("")
+            except Exception, e:
+                printer.error(e)
+                if cccli.debug:
+                    raise
+                else:
+                    printer.error("This is a not expected error, please report it!")
+        try:
+            pass
+            #readline.write_history_file(self._settings["histfile"])
+        except IOError:
+            pass
+        printer.out("Tcho!")
+
+    def _parser(self):
+        '''Non interactive parser'''
+        while True:
+            line = raw_input(self._prompt)
+            self._parse_line(line)
+
+    def _parse_line(self, line):
+        '''Parse a line (more than one command)'''
+        for cmd in line.split(";"):
+            if (cmd.strip() == "" or cmd[0] == "#"):
+                continue
+            elif (cmd[0] == "!"):
+                p = subprocess.Popen(cmd[1:], close_fds=True, shell=True)
+                p.wait()
+                ret = p.returncode
+            elif (cmd[0] == "?"):
+                command.Command("help").call()
+            else:
+                argv = self._lex_argv(cmd)
+                command.Command(argv, self._rpc).call()
+
+    def _lex_argv(self, string):
+        return string.split(" ")
diff --git a/cccli/clierror.py b/cccli/clierror.py
index 0f27d35..1ffd244 100644
--- a/cccli/clierror.py
+++ b/cccli/clierror.py
@@ -8,3 +8,6 @@ CloudControl CLI Errors
 ################################################################################
 class cliError(Exception):
     pass
+
+class BadCommand(cliError):
+    pass
diff --git a/cccli/command.py b/cccli/command.py
new file mode 100644
index 0000000..fbf6656
--- /dev/null
+++ b/cccli/command.py
@@ -0,0 +1,54 @@
+import re
+from sjrpc.client import SimpleRpcClient
+from cccli import printer
+from cccli.clierror import *
+
+class Command(object):
+
+    def __init__(self, argv, rpc):
+        if len(argv) < 1:
+            raise Exception("Empty command")
+        self._argv = argv
+        self._rpc = rpc
+
+    def call(self):
+        '''Run command'''
+        if re.match("^[a-zA-Z0-9]+$", self._argv[0]):
+            name = "cmd_%s"%self._argv[0]
+            if hasattr(self, name):
+                cmd = getattr(self, name)
+                return cmd(self._argv)
+        raise BadCommand(self._argv[0])
+
+    def usage(self, cmdname, printf=None):
+        '''Return usage of a command'''
+        fname = "cmd_%s"%cmdname
+        if hasattr(self, fname):
+            if hasattr(getattr(self, fname), "usage"):
+                s = getattr(getattr(self, fname), "usage")
+                if printf != None:
+                    printf(s)
+                return s
+            else:
+                return "No usage"
+        else:
+            raise myError("No such command: %s"%cmdname)
+
+    def cmd_exit(self, argv):
+        '''Quit application with respect'''
+        raise SystemExit()
+
+    def cmd_list(self, argv):
+        '''List something'''
+        if len(argv) == 1:
+            argv.append("hv")
+        items = self._rpc.list(str.join("", argv[1:]))
+        for item in items:
+            for key, val in item.items():
+                printer.out("%s: %s "%(key, val))
+            printer.out("*"*80)
+
+    def cmd_export(self, argv):
+        '''List server exported methods'''
+        for cmds in self._rpc.list_commands():
+            printer.out("%s"%cmds["name"])
diff --git a/cccli/parser.py b/cccli/parser.py
index d430c81..e461a30 100644
--- a/cccli/parser.py
+++ b/cccli/parser.py
@@ -31,6 +31,7 @@ class Parser(object):
 
     def _prompt(self):
         '''Show a parser prompt'''
+        return "> "
         bang = "#" if Keyring().has_unlocked_key() else "$"
         try:
             cwd = re.sub(self.rootpath, '~', os.getcwd())
diff --git a/cccli/printer.py b/cccli/printer.py
index 88404a1..b35657b 100644
--- a/cccli/printer.py
+++ b/cccli/printer.py
@@ -6,6 +6,8 @@ CloudControl CLI Printers
 '''
 
 import sys
+import os
+import cccli
 
 ###############################################################################
 color = {
@@ -37,38 +39,42 @@ color = {
     }
 
 ###############################################################################
-def out(message, fd=sys.stdout, nl="\n"):
+def out(message, fd=sys.stdout, nl=os.linesep):
     '''Print a message in fd ended by nl'''
-    fd.write("%s%s"%(message,nl))
+    fd.write("%s%s"%(message, nl))
     fd.flush()
 
 ###############################################################################
-def err(message, fd=sys.stderr, nl="\n"):
+def err(message, fd=sys.stderr, nl=os.linesep):
     out(message, fd, nl)
 
 ###############################################################################
-def fatal(message, exitcode=42, fd=sys.stderr, nl="\n"):
-    out("%sFatal Error:%s %s.%s"%(color["lred"],
-                                  color["red"],
-                                  message,
-                                  color["reset"])
-        ,fd,nl)
+def fatal(message, exitcode=42, fd=sys.stderr, nl=os.linesep):
+    out("%sFatal%s: %s%s"%(color["lred"],
+                           color["red"],
+                           message,
+                           color["reset"])
+        , fd, nl)
     if exitcode >= 0:
-        sys.exit(exitcode)
+        exit(exitcode)
 
 ###############################################################################
-def error(message, fd=sys.stderr, nl="\n"):
-    out("%sError:%s %s.%s"%(color["lred"],
-                              color["red"],
-                              message,
-                              color["reset"])
+def error(message, fd=sys.stderr, nl=os.linesep):
+    out("%sError%s: %s.%s"%(color["lred"],
+                            color["red"],
+                            message,
+                            color["reset"])
         ,fd,nl)
 
 ###############################################################################
-def warn(message, fd=sys.stderr, nl="\n"):
-    out("%sWarning:%s %s.%s"%(color["lyellow"],
+def warn(message, fd=sys.stderr, nl=os.linesep):
+    out("%sWarning%s: %s.%s"%(color["lyellow"],
                               color["yellow"],
                               message,
                               color["reset"])
         ,fd,nl)
 
+###############################################################################
+def debug(message, fd=sys.stderr, nl=os.linesep):
+    if cccli.debug:
+        out(message, fd, nl)
diff --git a/debian/control b/debian/control
index 53a6435..24c1a8f 100644
--- a/debian/control
+++ b/debian/control
@@ -3,13 +3,13 @@ Section: python
 Priority: optional
 Maintainer: Sebastien Luttringer <sebastien.luttringer@smartjog.com>
 Uploaders: Sebastien Luttringer <sebastien.luttringer@smartjog.com>
-Build-Depends: debhelper (>= 7), python-central (>= 0.6), cdbs (>= 0.4.50), python-setuptools, python
+Build-Depends: debhelper (>= 7), python-central (>= 0.6), cdbs (>= 0.4.50), python-setuptools, python (<< 3.0)
 XS-Python-Version: >= 2.6
 Standards-Version: 3.8.0
 
 Package: cc-cli
 Architecture: all
-Depends: ${misc:Depends}, ${python:Depends}
+Depends: ${misc:Depends}, ${python:Depends}, python (<< 3), python-sjrpc (>= 4)
 XB-Python-Version: ${python:Versions}
 Description: CloudControl CLI
  This package provides the Command Line Interface to CloudControl.
-- 
GitLab