#!/usr/bin/python2.2
# ZShellCLI - (c) 2001 Andy McKay
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
#
# This software is now part of Jerome Alet's ZShell

#
# $Id: ZShellCLI.py,v 1.4 2001/08/30 13:09:10 jerome Exp $
#
# $Log: ZShellCLI.py,v $
# Revision 1.4  2001/08/30 13:09:10  jerome
# CVS Id and Log markers added
#
#

import cmd, sys, os, tempfile, atexit, xmlrpclibBasicAuth, readline, re
# for python2.3, add rlcompleter to import list, drop shutil

default_prompt = '> '

class Completer:
# for python2.3, change signature to Completer(rlcompleter.Completer)
    """__init__ is backported from python2.3 to allow namespace definition
        for tab completion. (drop for python2.3)
    """
    def __init__(self, namespace = None):
         """Completer Factory -- create a completer using Completer(namspace)
  
         namespace is optional.  It is the namspace in which completions are
         performed.  If unspecified, it is set to __main__.__dict__.
         Namespaces are dictionaries.

         A Completer instance is used as the completion mechanisim of
         readline.  Format:

         readline.set(complter(Completer(my_namespace).complete)
         """

         if namespace and not isinstance(namespace, dict):
             raise TypeError, 'Namepsace must be a dictionary.'

         # Very late binding is desired for main_dict, so we will flag
         # it and bind at completion time if no dictionary has been passed.
         # if a dictionary has been given us, bind now.
         self.use_main_ns = 1
         if namespace:
             self.use_main_ns = 0
             self.namespace = namespace

    def global_matches(self, text):
        matches = []
        n = len(text)
        for list in [self.namespace]:
            for word in list:
                if word[:n] ==  text:
                    matches.append(word)
        return matches
        
    def complete(self, text, state):
        if state ==  0:
             self.matches = self.global_matches(text)
        try:
             return self.matches[state]
        except IndexError:
             return None

    def attr_matches(self, text):
        """Computer matches it the text contains a dot.
        
        Assuming text is of form Name1.Name2....[Namen], and can be
        evaluated in self.namespace, the evaluation is done, and
        the is dir() and any class members are appended to the match
        list.
        
        WARNING:  if the object implements the __getattr__ hook, aribtrary
        C code may be invoked.
        """
        m = re.match(r'\w+(\.\w+)*\.(\w*)', text)
        if not m:
            return None
        expr, attr = m.group(1, 3)
        object = eval(expr, self.namespace)
        words = dir(object)
        if hasattr(object, '__class__'):
            words.append('__class__')
            words+= get_class_members(object.__class__)
        matches = []
        for word in words:
            if word[:n] == attr and word!= '__builtins__':
                matches.append('%s.%s' % (expr, word) )
        return matches

class ZShellCLI(cmd.Cmd):
    def __init__(self, *args):
        """ setup """
        self.prompt = default_prompt
        self.path = ''
        self.connect = None
        self.completekey = 'tab'
        self.cmdqueue = []
        self.stdout = sys.stdout

    def do_open(self, line):
        """
	The password is typed in in clear text, and is stored
	in a protected directory in your home directory (on unix).
	On windows, it is stored in c:\, and security is left up
	to you!
        """
        # syntax: open http://127.0.0.1 user pwd
        (url, user, pwd) = line.split(' ')
        self.base_prompt = url.split('/')[-1]
        self.prompt = self.base_prompt + '> '
        self.connect = xmlrpclibBasicAuth.Server(url, user, pwd)
        print "Connected to %s" % url
        readline.set_completer(mycompleter(shell.complete()).complete)

    def do_close(self, line):
        self.connect = None
        print "Disconnected from %s" % self.base_prompt
        self.prompt = default_prompt

    def do_exit(self, line):
        sys.exit() # bye

    def do_quit(self, line):
        sys.exit() # bye

    # I think that this function is dead code!  (jwp)
    def show_browser(self, data):
        """ creates a make file and shoves it in """
        filename = tempfile.mktemp()
        file = open(filename, 'w')
        data = '\n'.join(data.split('\n')[1:], '\n')
        file.write(data)
        file.close()
        os.system('explorer %s' % filename)

    def set_path(self, path):
        """ change path, set a nice prompt """
        self.path = path
        self.prompt = "%s%s> " % (self.base_prompt, self.path)

    # TODO : create a way edit files directly from zshellcli
    # this may be done using an edit command, or possibly using
    # the do_shell method of Cmd.
    def run_cmd(self, command):
        """ runs a command, changes path """
        if self.connect is not None:
	    oldpath = self.path
            info = { 'command': command, 'result_type': 'text', 'path': self.path }
            output = self.connect.zshell('', info)
            self.set_path(output['path'])
	    # update completion list if in a new dictionary
	    if self.path != oldpath:
	        readline.set_completer(mycompleter(self.complete()).complete)
            return output['data']
        else:
            return "Not Connected"

    def default(self, line):
        print self.run_cmd(command = line)

    def emptyline(self):
        pass

    def complete(self):
        try:
           a = self.run_cmd('ls').split('\n')
        except:
           return {'':''}
        if len(a) < 2:
           return {'':''}
        b = {}
        for x in a:
            try:
	       # look only at the last affix of the name to do completion.
               m = re.search('.*/(.*)', x).group(1)
               b[m] = 1
            except:
               pass
        return b

    def preloop(self):
        """ hello """
        print
        print "ZShellCLI: A CLI to ZShell"

mycompleter = Completer
# start with empty dictionary (this is changed in do_open and run_cmd)
readline.set_completer(mycompleter({'':''}).complete)
readline.parse_and_bind("tab: complete")
if os.name=='posix':
    zshelldir = os.path.join(os.environ['HOME'], '.zope-shell')
else:
   # this has not been tested, and may not be appropriate for other
   # OSes.  Feedback appreciated.
    zshelldir = 'c:\\'
 
if not os.path.exists(zshelldir):
    try:
        # accessible only to current user
        os.mkdir(zshelldir, 0700)
    except:
        print """Warning:  could not create directory %s; 
  will not be able to save history.""" % zshelldir
        zshelldir=''

# we create a tmp directory in zshelldir.  This is not used at the moment,
# but will be used when editting is enabled, and it takes pretty much no
# space, so we will do it now.
if zshelldir:
    zshelltmpdir = os.path.join(zshelldir, 'tmp')
    if not os.path.exists(zshelltmpdir):
        try:
	    os.mkdir(zshelltmpdir, 0700)
	except:
	    print """Warning:  could not create directory %s;
  file editting will not work.""" % zshelltmpdir

# seed with current history file.
if zshelldir and os.path.exists(zshelldir):
    histfile = os.path.join(zshelldir, 'history')
    atexit.register(readline.write_history_file, histfile)
    try:
        readline.read_history_file(histfile)
    except:
        pass

if __name__ ==  '__main__':
    shell = ZShellCLI()
    try:
        # go!
        shell.cmdloop()
    except KeyboardInterrupt:
        sys.exit()

