# Python parser for Subversion skels

def thaw(str):
  if str[0] != '(' and str[-1] != ')':
    raise ValueError("Improperly bounded skel")
  wholeskel = str
  str = str[1:-1].lstrip()
  prev_accums = []
  accum = []
  while 1:
    if len(str) == 0:
      return accum
    if "0123456789".find(str[0]) > -1:
      split_tuple = str.split(' ',1)
      count = int(split_tuple[0])
      if len(split_tuple) > 1:
        str = split_tuple[1]
      else:
        str = ""
      accum.append(str[:count])
      str = str[count:].lstrip()
      continue
    if "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".find(str[0]) > -1:
      def m1eos(x):
        if x == -1:
          return len(str)
        else:
          return x
      idx = min(m1eos(str.find(' ')), m1eos(str.find('(')),
          m1eos(str.find(')')))
      accum.append(str[:idx])
      str = str[idx:].lstrip()
      continue
    if str[0] == '(':
      new_accum = []
      accum.append(new_accum)
      prev_accums.append(accum)
      accum = new_accum
      str = str[1:].lstrip()
      continue
    if str[0] == ')':
      accum = prev_accums.pop()
      str = str[1:].strip()
      continue
    if str[0] == ' ':
      str = str.lstrip(' ')
      continue
    raise ValueError("Unexpected contents in skel: '%s'\n'%s'" % (str, wholeskel))

class Rev:
  def __init__(self, skelstring):
    sk = thaw(skelstring)
    if len(sk) == 2 and sk[0] == "revision" and type(sk[1]) == str:
      self.txn = sk[1]
    else:
      raise ValueError("Invalid revision skel: %s" % skelstring)

class Change:
  def __init__(self,skelstring):
    sk = thaw(skelstring)
    if len(sk) == 6 and sk[0] == "change" and type(sk[1]) == type(sk[2]) \
        == type(sk[3]) == type(sk[4]) == type(sk[5]) == str:
          self.path = sk[1]
          self.node = sk[2]
          self.kind = sk[3]
          self.textmod = sk[4]
          self.propmod = sk[5]
    else:
      raise ValueError("Invalid change skel: %s" % skelstring)

class Copy:
  def __init__(self,skelstring):
    sk = thaw(skelstring)
    if len(sk) == 4 and sk[0] in ("copy", "soft-copy") and type(sk[1]) \
        == type(sk[2]) == type(sk[3]) == str:
          self.kind = sk[0]
          self.srcpath = sk[1]
          self.srctxn = sk[2]
          self.destnode = sk[3]
    else:
      raise ValueError("Invalid copy skel: %s" % skelstring)

class Node:
  def __init__(self,skelstring):
    sk = thaw(skelstring)
    if (len(sk) == 3 or (len(sk) == 4 and type(sk[3]) == str)) \
        and type(sk[0]) == list and type(sk[1]) == str \
        and type(sk[2]) == str and sk[0][0] in ("file", "dir") \
        and type(sk[0][1]) == type(sk[0][2]) == type(sk[0][3]) == str:
          self.kind = sk[0][0]
          self.createpath = sk[0][1]
          self.prednode = sk[0][2]
          self.predcount = int(sk[0][3])
          self.proprep = sk[1]
          self.datarep = sk[2]
          if len(sk) > 3:
            self.editrep = sk[3]
          else:
            self.editrep = None
    else:
      raise ValueError("Invalid node skel: %s" % skelstring)

class Txn:
  def __init__(self,skelstring):
    sk = thaw(skelstring)
    if len(sk) == 5 and sk[0] in ("transaction", "committed", "dead") \
        and type(sk[1]) == type(sk[2]) == str \
        and type(sk[3]) == type(sk[4]) == list and len(sk[3]) % 2 == 0:
          self.kind = sk[0]
          self.rootnode = sk[1]
          self.basenode = self.rev = sk[2]
          self.proplist = sk[3]
          self.copies = sk[4]
    else:
      raise ValueError("Invalid transaction skel: %s" % skelstring)

class Rep:
  def __init__(self,skelstring):
    sk = thaw(skelstring)
    if type(sk[0]) == list and len(sk[0]) == 3 and type(sk[0][1]) == str \
        and type(sk[0][2]) == list and len(sk[0][2]) == 2 \
        and type(sk[0][2][0]) == type(sk[0][2][1]) == str:
          self.kind = sk[0][0]
          self.txn = sk[0][1]
          self.cksumtype = sk[0][2][0]
          self.cksum = sk[0][2][1]
          if len(sk) == 2 and sk[0][0] == "fulltext":
            self.str = sk[1]
          elif len(sk) >= 2 and sk[0][0] == "delta":
            self.windows = sk[1:]
          return
    raise ValueError("Invalid representation skel: %s" % skelstring)


