#!/usr/bin/python2.3

# Copyright 2003 Iustin Pop
#
# This file is part of cfvers.
#
# cfvers 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.
#
# cfvers 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 cfvers; if not, write to the Free Software Foundation,
# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

import tempfile
import sets
import re
import fnmatch
from mx import DateTime

import cfvers
try:
    from optparse import OptionParser
except ImportError:
    from optik import OptionParser
    
import os, os.path
import sys
import types

class CLI(object):
    def __init__(self):
        commands = {
            'find':     (self.cmd_find, ""),
            'log':      (self.cmd_log, "[files...]"),
            'store':    (self.cmd_store, "[files...]"),
            'retrieve': (self.cmd_retrieve, "[files...]"),
            'cat':      (self.cmd_cat, "<filename>"),
            'diff':     (self.cmd_diff, "[files...]"),
            'stat':     (self.cmd_stat, "[files...]"),
            }
        
        self.curr_cmd = "<command>"
        self.usage="usage: %prog [global options] command [command options and arguments]\nwhere command is one of\n"
        l = commands.keys()
        l.sort()
        for i in l:
            self.usage += "    %-10s %s\n" % (i, commands[i][1])
        self.usage += "\nGlobal options:"
        self.commands = commands
        return

    def get_scp(self, earg):
        """Returns a sub-command parser"""
        op = OptionParser(version=cfvers.cmd.CLIScript.get_version(),
                          usage="%%prog [global options] %s [options] " \
                          "%s\nFor global options, see %%prog --help" \
                          % (self.curr_cmd, earg))
        return op

    def main(self, argv):
        op = OptionParser(version=cfvers.cmd.CLIScript.get_version(),
                          usage=self.usage)
        op.disable_interspersed_args()

        op.add_option("-a", "--area", dest="area",
                      help="the area to work on, as [server:]name",
                      type="string", default=None,
                      metavar="AREASPEC")

        op.add_option("-d", dest="repository",
                      help="the repository path",
                      type="string", default=None,
                      metavar="REPOSITORY")

        (options, args) = op.parse_args(argv)
        if len(args) == 0:
            op.print_help()
            return
        command = args.pop(0)
        if not command in self.commands:
            cfound = []
            for fcom in self.commands:
                if fcom.startswith(command):
                    cfound.append(fcom)
            if len(cfound) > 1:
                print >> sys.stderr, "Ambigous command '%s' (%s)" % (command, ",".join(cfound))
                op.print_help
                return
            elif len(cfound) == 0:
                print >>sys.stderr, "Unknown command %s" % command
                op.print_help()
                return
            command = cfound[0]
        try:
            self.cmdi = cfvers.Commands(options)
        except cfvers.ConfigException, e:
            print >>sys.stderr, "Error: your configuration is invalid.\nError message: %s." % e
            return
        except Exception, e:
            print >>sys.stderr,"Error while initializing: %s" % e
            raise
            return
        self.curr_cmd = command
        func = self.commands[command][0]
        func(options, args)
        self.cmdi.close()
        return
        
    def cmd_find(self, options, args):
        """Searches in the repository"""
        op = self.get_scp("[files...]")
        op.add_option("-d", "--detail", dest="detail",
                      help="shows detailed information",
                      default=None, action="store_true",
                      )
        op.add_option("-l", "--long", dest="long",
                      help="shows 'ls -ld' like details",
                      default=None, action="store_true",
                      )
        op.add_option("-r", "--revno", dest="revno",
                      help="only show items which are available " \
                      "in this revision number",
                      metavar="REVNO", type="int", default=None)
        op.add_option("--name", dest="name",
                      help="name matches shell-glob NAME",
                      metavar="NAME", type="string",
                      default=None)
        op.add_option("--regexp", dest="regexp",
                      help="name matches regular expression REGEXP",
                      metavar="REGEXP", type="string",
                      default=None)
        (cmdopts, cmdargs) = op.parse_args(args)
        if cmdopts.regexp is not None:
            cmdopts.regexp = re.compile(cmdopts.regexp)
        r = self.cmdi.repo
        for i in r.items(area=self.cmdi.area):
            if cmdopts.revno is not None and \
                   r.getEntry(i, cmdopts.revno) is None:
                continue
            if cmdopts.regexp is not None and \
                   not cmdopts.regexp.match(i.name):
                continue
            if cmdopts.name is not None and \
                   not fnmatch.fnmatch(i.name, cmdopts.name):
                continue
            if cmdopts.detail:
                print "-" * 25
                print i
                print "Created at: %s" % (i.ctime.strftime("%FT%T+0"))
                revs = r.getRevNumbers(i)
                print "Has revisions: %s" % revs
                print
            elif cmdopts.long:
                revs = r.getRevNumbers(i)
                if len(revs) == 0:
                    print "%s %5d %8s %8s %5d %s %s" % \
                          ('----------', 0, 'N/A', 'N/A',
                           0, "N/A", i.name)
                else:
                    lrev = revs[-1]
                    entry = r.getEntry(i, lrev)
                    m = DateTime.TimestampFromTicks(entry.mtime)
                    print "%s %5d %8s %8s %5d %s %s" % \
                          (entry.mode2str(), len(revs),
                           entry.uid, entry.gid,
                           entry.size,
                           m.strftime("%Y-%m-%d %T"), i.name)
            else:
                print i.name
        return

    def cmd_store(self, options, args):
        """Store some items in the repository"""
        op = self.get_scp("[files...]")
        op.add_option("-c", "--commiter", dest="commiter",
                      help="commiter information",
                      type="string", default=None)
        op.add_option("-m", "--logmsg", dest="logmsg",
                      help="log message",
                      type="string", default=None,
                      metavar="MESSAGE")
        op.add_option("-n", "--no-recurse", dest="norecurse",
                      help="disable recursion on directories",
                      action="store_true", default=False)
        op.add_option("-q", "--quiet", dest="quiet",
                      help="only display errors",
                      action="store_true", default=False)
        (storeoptions, storeargs) = op.parse_args(args)
        if storeoptions.logmsg is None:
            if sys.stdin.isatty() and sys.stdout.isatty():
                nt = self.get_text("")
                if len(nt) == 0:
                    print >> sys.stderr, "Commit aborted."
                    return
                storeoptions.logmsg = nt
            else:
                print >> sys.stderr, "Log message not specified and stdin/stout are not ttys."
                print >> sys.stderr, "Commit aborted."
                return
        errors, store_done = self.cmdi.store(files=storeargs, options=storeoptions)
        
        if len(errors) > 0:
            if store_done:
                print >>sys.stderr, "Warnings appeared during store:"
            else:
                print >>sys.stderr, "Store aborted. The following errors have occured:"
            for e in errors:
                try:
                    name = e[2].name
                except AttributeError:
                    name = e[2]
                if e[0] == cfvers.STORED_IOERROR:
                    print >>sys.stderr, "IOError:",
                elif e[0] == cfvers.STORED_NOTCHANGED:
                    print >>sys.stderr, "Not changed:",
                print >>sys.stderr, name, e[1]
        return

    def cmd_retrieve(self, options, args):
        """Retrieve some items from the repository"""
        op = self.get_scp("[files...]")
        op.add_option("-d", "--destdir", dest="destdir",
                      help="destination directory",
                      type="string", default=None,
                      metavar="PATH")
        op.add_option("-n", "--no-recurse", dest="norecurse",
                      help="disable recursion on directories restored",
                      action="store_true", default=False)
        op.add_option("-q", "--quiet", dest="quiet",
                      help="only display errors",
                      action="store_true", default=False)
        op.add_option("-r", "--revno", dest="revno",
                      help="revision number to be retrieved",
                      metavar="REVNO", type="int")
        op.add_option("-s", "--skip-dirs", dest="use_dirs",
                      help="together with -d, extract files directly under PATH, ignoring their stored paths",
                      default=1, action="store_false")

        (retroptions, retrargs) = op.parse_args(args)
        self.cmdi.retrieve(files=retrargs, options=retroptions)
        return

    def cmd_diff(self, options, args):
        """Show the difference between versions for selected items"""
        op = self.get_scp("filename...")
        op.add_option("-r", "--revno", dest="rev",
                      help="revision number(s) to be compared",
                      metavar="REVNO[:REVNO]",
                      default=None
                      )
        op.add_option("-l", "--list", dest="list",
                      help="just list filenames which are different",
                      action="store_true", default=False,
                      )
        
        (diffoptions, diffargs) = op.parse_args(args)
        self.cmdi.diff(options=diffoptions, files=diffargs)
        return

    def cmd_log(self, options, args):
        """Show the editing history for selected items"""
        op = self.get_scp("[files...]")
        op.add_option("-r", "--revno", dest="rev",
                      help="revision number(s) to be compared",
                      metavar="REVNO[:REVNO]",
                      default=None
                      )
        (cmdopts, cmdargs) = op.parse_args(args)
        arearevs = self.cmdi.log()
        r = self.cmdi.repo
        if len(cmdargs) > 0:
            itemrevs = sets.Set()
            for itname in cmdargs:
                item = r.getItemByName(self.cmdi.area, itname)
                if item is None:
                    raise ValueError, "Item %s not found in repository!" % itname
                itemrevs |= sets.Set(r.getRevNumbers(item))
        else:
            itemrevs = sets.Set([x.revno for x in arearevs])
        if cmdopts.rev is not None:
            r1, r2 = cfvers.cmd.CLIScript.parserev(cmdopts.rev)
            if r1 is not None and r2 is not None:
                givenrevs = sets.Set(range(r1, r2+1))
            elif r1 is None and r2 is not None:
                givenrevs = sets.Set([r2])
            elif r1 is not None and r2 is None:
                givenrevs = sets.Set([r1])
            selrevs = itemrevs & givenrevs
        else:
            selrevs = itemrevs
        for ar in arearevs:
            if ar.revno in selrevs:
                print "-" * 80
                print "Revision number:  %d" % ar.revno
                print "Source server:    %s" % ar.server
                print "Date entered:     %s %s" % (ar.ctime.localtime().strftime("%F %T"), ar.ctime.tz)
                print "Commiter info:    %s" % ar.commiter
                print "Commiter uid/gid: %d/%d" % (ar.uid, ar.gid)
                print "Modified items:"
                for id in r.getAreaRevItems(ar):
                    item = r.getItem(id)
                    res = r.getRevNumbers(item)
                    if res[0] == ar.revno:
                        char = "A"
                    else:
                        char = "M"
                    print "   %c %s" % (char, item.name)
                print "Log message:"
                print ar.logmsg
        if len(selrevs) > 0:
            print "-" * 80
        return

    def cmd_cat(self, options, args):
        """Displays the contents of selected items"""
        op = self.get_scp("filename")
        op.add_option("-r", "--revno", dest="rev",
                      help="revision number to be shown",
                      metavar="REVNO")
        (catoptions, catargs) = op.parse_args(args)
        if len(catargs) == 0:
            print >>sys.stderr, "Missing filename!"
            sys.exit(1)
        if len(catargs) > 1:
            print >>sys.stderr, "Only one filename allowed!"
            sys.exit(1)
        try:
            rev = catoptions.rev
        except ValueError:
            print >>sys.stderr, "Invalid revision number '%s'" % catoptions.rev
            sys.exit(1)
        self.cmdi.show(catargs[0], rev=rev)
        return

    def cmd_stat(self, options, args):
        op = self.get_scp("[files...]")
        op.add_option("-r", "--revno", dest="rev",
                      help="revision number to be shown",
                      metavar="REVNO")
        (cmdopts, cmdargs) = op.parse_args(args)
        self.cmdi.stat(cmdopts, cmdargs)
        return
        
    def get_text(self, extratext):
        """Method to get a text from the user"""
        fd, fname = tempfile.mkstemp()
        fo = os.fdopen(fd, "w+")
        guardian = "---- This line and the rest below won't be included ----"
        fo.write("\n")
        fo.write(guardian)
        fo.write("\n")
        fo.write(extratext)
        fo.flush()
        editor=os.getenv("EDITOR")
        if editor is None:
            editor="vi"
        try:
            os.system("%s %s" % (editor,fname))
            fo.seek(0, 0)
            ndata = fo.read()
            lin = ndata.split("\n")
            realtext = []
            for i in lin:
                if i != guardian:
                    realtext.append(i)
                else:
                    break
            if len(realtext) > 0 and realtext[-1] == "\n":
                realtext.pop()
            return "\n".join(realtext)
        finally:
            fo.close()
            os.unlink(fname)
        return ""
            

if __name__ == "__main__":
    os.stat_float_times(False)
    cli = CLI()
    cli.main(sys.argv[1:])
