symbian-qemu-0.9.1-12/python-2.6.1/Demo/pdist/rcslib.py
changeset 1 2fb8b9db1c86
equal deleted inserted replaced
0:ffa851df0825 1:2fb8b9db1c86
       
     1 """RCS interface module.
       
     2 
       
     3 Defines the class RCS, which represents a directory with rcs version
       
     4 files and (possibly) corresponding work files.
       
     5 
       
     6 """
       
     7 
       
     8 
       
     9 import fnmatch
       
    10 import os
       
    11 import re
       
    12 import string
       
    13 import tempfile
       
    14 
       
    15 
       
    16 class RCS:
       
    17 
       
    18     """RCS interface class (local filesystem version).
       
    19 
       
    20     An instance of this class represents a directory with rcs version
       
    21     files and (possible) corresponding work files.
       
    22 
       
    23     Methods provide access to most rcs operations such as
       
    24     checkin/checkout, access to the rcs metadata (revisions, logs,
       
    25     branches etc.) as well as some filesystem operations such as
       
    26     listing all rcs version files.
       
    27 
       
    28     XXX BUGS / PROBLEMS
       
    29 
       
    30     - The instance always represents the current directory so it's not
       
    31     very useful to have more than one instance around simultaneously
       
    32 
       
    33     """
       
    34 
       
    35     # Characters allowed in work file names
       
    36     okchars = string.ascii_letters + string.digits + '-_=+'
       
    37 
       
    38     def __init__(self):
       
    39         """Constructor."""
       
    40         pass
       
    41 
       
    42     def __del__(self):
       
    43         """Destructor."""
       
    44         pass
       
    45 
       
    46     # --- Informational methods about a single file/revision ---
       
    47 
       
    48     def log(self, name_rev, otherflags = ''):
       
    49         """Return the full log text for NAME_REV as a string.
       
    50 
       
    51         Optional OTHERFLAGS are passed to rlog.
       
    52 
       
    53         """
       
    54         f = self._open(name_rev, 'rlog ' + otherflags)
       
    55         data = f.read()
       
    56         status = self._closepipe(f)
       
    57         if status:
       
    58             data = data + "%s: %s" % status
       
    59         elif data[-1] == '\n':
       
    60             data = data[:-1]
       
    61         return data
       
    62 
       
    63     def head(self, name_rev):
       
    64         """Return the head revision for NAME_REV"""
       
    65         dict = self.info(name_rev)
       
    66         return dict['head']
       
    67 
       
    68     def info(self, name_rev):
       
    69         """Return a dictionary of info (from rlog -h) for NAME_REV
       
    70 
       
    71         The dictionary's keys are the keywords that rlog prints
       
    72         (e.g. 'head' and its values are the corresponding data
       
    73         (e.g. '1.3').
       
    74 
       
    75         XXX symbolic names and locks are not returned
       
    76 
       
    77         """
       
    78         f = self._open(name_rev, 'rlog -h')
       
    79         dict = {}
       
    80         while 1:
       
    81             line = f.readline()
       
    82             if not line: break
       
    83             if line[0] == '\t':
       
    84                 # XXX could be a lock or symbolic name
       
    85                 # Anything else?
       
    86                 continue
       
    87             i = string.find(line, ':')
       
    88             if i > 0:
       
    89                 key, value = line[:i], string.strip(line[i+1:])
       
    90                 dict[key] = value
       
    91         status = self._closepipe(f)
       
    92         if status:
       
    93             raise IOError, status
       
    94         return dict
       
    95 
       
    96     # --- Methods that change files ---
       
    97 
       
    98     def lock(self, name_rev):
       
    99         """Set an rcs lock on NAME_REV."""
       
   100         name, rev = self.checkfile(name_rev)
       
   101         cmd = "rcs -l%s %s" % (rev, name)
       
   102         return self._system(cmd)
       
   103 
       
   104     def unlock(self, name_rev):
       
   105         """Clear an rcs lock on NAME_REV."""
       
   106         name, rev = self.checkfile(name_rev)
       
   107         cmd = "rcs -u%s %s" % (rev, name)
       
   108         return self._system(cmd)
       
   109 
       
   110     def checkout(self, name_rev, withlock=0, otherflags=""):
       
   111         """Check out NAME_REV to its work file.
       
   112 
       
   113         If optional WITHLOCK is set, check out locked, else unlocked.
       
   114 
       
   115         The optional OTHERFLAGS is passed to co without
       
   116         interpretation.
       
   117 
       
   118         Any output from co goes to directly to stdout.
       
   119 
       
   120         """
       
   121         name, rev = self.checkfile(name_rev)
       
   122         if withlock: lockflag = "-l"
       
   123         else: lockflag = "-u"
       
   124         cmd = 'co %s%s %s %s' % (lockflag, rev, otherflags, name)
       
   125         return self._system(cmd)
       
   126 
       
   127     def checkin(self, name_rev, message=None, otherflags=""):
       
   128         """Check in NAME_REV from its work file.
       
   129 
       
   130         The optional MESSAGE argument becomes the checkin message
       
   131         (default "<none>" if None); or the file description if this is
       
   132         a new file.
       
   133 
       
   134         The optional OTHERFLAGS argument is passed to ci without
       
   135         interpretation.
       
   136 
       
   137         Any output from ci goes to directly to stdout.
       
   138 
       
   139         """
       
   140         name, rev = self._unmangle(name_rev)
       
   141         new = not self.isvalid(name)
       
   142         if not message: message = "<none>"
       
   143         if message and message[-1] != '\n':
       
   144             message = message + '\n'
       
   145         lockflag = "-u"
       
   146         if new:
       
   147             f = tempfile.NamedTemporaryFile()
       
   148             f.write(message)
       
   149             f.flush()
       
   150             cmd = 'ci %s%s -t%s %s %s' % \
       
   151                   (lockflag, rev, f.name, otherflags, name)
       
   152         else:
       
   153             message = re.sub(r'([\"$`])', r'\\\1', message)
       
   154             cmd = 'ci %s%s -m"%s" %s %s' % \
       
   155                   (lockflag, rev, message, otherflags, name)
       
   156         return self._system(cmd)
       
   157 
       
   158     # --- Exported support methods ---
       
   159 
       
   160     def listfiles(self, pat = None):
       
   161         """Return a list of all version files matching optional PATTERN."""
       
   162         files = os.listdir(os.curdir)
       
   163         files = filter(self._isrcs, files)
       
   164         if os.path.isdir('RCS'):
       
   165             files2 = os.listdir('RCS')
       
   166             files2 = filter(self._isrcs, files2)
       
   167             files = files + files2
       
   168         files = map(self.realname, files)
       
   169         return self._filter(files, pat)
       
   170 
       
   171     def isvalid(self, name):
       
   172         """Test whether NAME has a version file associated."""
       
   173         namev = self.rcsname(name)
       
   174         return (os.path.isfile(namev) or
       
   175                 os.path.isfile(os.path.join('RCS', namev)))
       
   176 
       
   177     def rcsname(self, name):
       
   178         """Return the pathname of the version file for NAME.
       
   179 
       
   180         The argument can be a work file name or a version file name.
       
   181         If the version file does not exist, the name of the version
       
   182         file that would be created by "ci" is returned.
       
   183 
       
   184         """
       
   185         if self._isrcs(name): namev = name
       
   186         else: namev = name + ',v'
       
   187         if os.path.isfile(namev): return namev
       
   188         namev = os.path.join('RCS', os.path.basename(namev))
       
   189         if os.path.isfile(namev): return namev
       
   190         if os.path.isdir('RCS'):
       
   191             return os.path.join('RCS', namev)
       
   192         else:
       
   193             return namev
       
   194 
       
   195     def realname(self, namev):
       
   196         """Return the pathname of the work file for NAME.
       
   197 
       
   198         The argument can be a work file name or a version file name.
       
   199         If the work file does not exist, the name of the work file
       
   200         that would be created by "co" is returned.
       
   201 
       
   202         """
       
   203         if self._isrcs(namev): name = namev[:-2]
       
   204         else: name = namev
       
   205         if os.path.isfile(name): return name
       
   206         name = os.path.basename(name)
       
   207         return name
       
   208 
       
   209     def islocked(self, name_rev):
       
   210         """Test whether FILE (which must have a version file) is locked.
       
   211 
       
   212         XXX This does not tell you which revision number is locked and
       
   213         ignores any revision you may pass in (by virtue of using rlog
       
   214         -L -R).
       
   215 
       
   216         """
       
   217         f = self._open(name_rev, 'rlog -L -R')
       
   218         line = f.readline()
       
   219         status = self._closepipe(f)
       
   220         if status:
       
   221             raise IOError, status
       
   222         if not line: return None
       
   223         if line[-1] == '\n':
       
   224             line = line[:-1]
       
   225         return self.realname(name_rev) == self.realname(line)
       
   226 
       
   227     def checkfile(self, name_rev):
       
   228         """Normalize NAME_REV into a (NAME, REV) tuple.
       
   229 
       
   230         Raise an exception if there is no corresponding version file.
       
   231 
       
   232         """
       
   233         name, rev = self._unmangle(name_rev)
       
   234         if not self.isvalid(name):
       
   235             raise os.error, 'not an rcs file %r' % (name,)
       
   236         return name, rev
       
   237 
       
   238     # --- Internal methods ---
       
   239 
       
   240     def _open(self, name_rev, cmd = 'co -p', rflag = '-r'):
       
   241         """INTERNAL: open a read pipe to NAME_REV using optional COMMAND.
       
   242 
       
   243         Optional FLAG is used to indicate the revision (default -r).
       
   244 
       
   245         Default COMMAND is "co -p".
       
   246 
       
   247         Return a file object connected by a pipe to the command's
       
   248         output.
       
   249 
       
   250         """
       
   251         name, rev = self.checkfile(name_rev)
       
   252         namev = self.rcsname(name)
       
   253         if rev:
       
   254             cmd = cmd + ' ' + rflag + rev
       
   255         return os.popen("%s %r" % (cmd, namev))
       
   256 
       
   257     def _unmangle(self, name_rev):
       
   258         """INTERNAL: Normalize NAME_REV argument to (NAME, REV) tuple.
       
   259 
       
   260         Raise an exception if NAME contains invalid characters.
       
   261 
       
   262         A NAME_REV argument is either NAME string (implying REV='') or
       
   263         a tuple of the form (NAME, REV).
       
   264 
       
   265         """
       
   266         if type(name_rev) == type(''):
       
   267             name_rev = name, rev = name_rev, ''
       
   268         else:
       
   269             name, rev = name_rev
       
   270         for c in rev:
       
   271             if c not in self.okchars:
       
   272                 raise ValueError, "bad char in rev"
       
   273         return name_rev
       
   274 
       
   275     def _closepipe(self, f):
       
   276         """INTERNAL: Close PIPE and print its exit status if nonzero."""
       
   277         sts = f.close()
       
   278         if not sts: return None
       
   279         detail, reason = divmod(sts, 256)
       
   280         if reason == 0: return 'exit', detail   # Exit status
       
   281         signal = reason&0x7F
       
   282         if signal == 0x7F:
       
   283             code = 'stopped'
       
   284             signal = detail
       
   285         else:
       
   286             code = 'killed'
       
   287         if reason&0x80:
       
   288             code = code + '(coredump)'
       
   289         return code, signal
       
   290 
       
   291     def _system(self, cmd):
       
   292         """INTERNAL: run COMMAND in a subshell.
       
   293 
       
   294         Standard input for the command is taken from /dev/null.
       
   295 
       
   296         Raise IOError when the exit status is not zero.
       
   297 
       
   298         Return whatever the calling method should return; normally
       
   299         None.
       
   300 
       
   301         A derived class may override this method and redefine it to
       
   302         capture stdout/stderr of the command and return it.
       
   303 
       
   304         """
       
   305         cmd = cmd + " </dev/null"
       
   306         sts = os.system(cmd)
       
   307         if sts: raise IOError, "command exit status %d" % sts
       
   308 
       
   309     def _filter(self, files, pat = None):
       
   310         """INTERNAL: Return a sorted copy of the given list of FILES.
       
   311 
       
   312         If a second PATTERN argument is given, only files matching it
       
   313         are kept.  No check for valid filenames is made.
       
   314 
       
   315         """
       
   316         if pat:
       
   317             def keep(name, pat = pat):
       
   318                 return fnmatch.fnmatch(name, pat)
       
   319             files = filter(keep, files)
       
   320         else:
       
   321             files = files[:]
       
   322         files.sort()
       
   323         return files
       
   324 
       
   325     def _remove(self, fn):
       
   326         """INTERNAL: remove FILE without complaints."""
       
   327         try:
       
   328             os.unlink(fn)
       
   329         except os.error:
       
   330             pass
       
   331 
       
   332     def _isrcs(self, name):
       
   333         """INTERNAL: Test whether NAME ends in ',v'."""
       
   334         return name[-2:] == ',v'