<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array"/>
  <modified type="array">
    <modified>
      <diff>@@ -1,7 +1,11 @@
 #!/usr/bin/python
-# ex: set ff=dos ts=2 et:
+# ex: set ff=dos ai ts=2 et:
 
 import os
+import popen2
+import string
+import re
+import stat
 
 # 
 # 
@@ -59,10 +63,11 @@ assert &quot;rwxcd&quot; == Perm().tostr(Perm.Read | Perm.Writ | Perm.Exec | Perm.Crea | P
 # 
 class Reason:
 
-  path  = &quot;&quot;    # 
-  label = 0     # ???
-  yes   = [ ]   # reasons why we CAN access file/dir
-  no    = [ ]   # reasons why we CAN'T
+  def __init__(self):
+    self.path  = &quot;&quot;    # 
+    self.label = 0     # ???
+    self.yes   = [ ]   # reasons why we CAN access file/dir
+    self.no    = [ ]   # reasons why we CAN'T
 
   # reasons why we don't have access
   class No:
@@ -129,63 +134,178 @@ assert &quot;Root&quot;       == Reason().Yes().toStr(Reason.Yes.Root)
 class File:
   pass
 
-
 class Group:
   NONE = -1
 
 class User:
   NONE = -1
-  name = &quot;&quot;
-  uid = 0
-  groups = [ ]
   def __init__(self, name):
-    self.name = name
+    self.name     = name
+    self.uid      = 0
+    self.groups   = [ ]
 
 class MountPoint:
-  dev = &quot;&quot;
-  dir = &quot;&quot;
+  # str is a line of output from 'mount' in the format:
+  #   /dev/sda3 on / type ext3 (rw,errors=remount-ro)
+  def __init__(self, str):
+    self.dev        = &quot;&quot;
+    self.path       = &quot;&quot;
+    self.filesystem = &quot;&quot;
+    self.params     = [ ]
+    self.isreadonly = False
+    self.isnoexec   = False
+    self.dev,_,self.path,_,self.filesystem,params = str.split()
+    if len(params) &gt; 1:
+      params = params[1:-1].split(&quot;,&quot;)
+      for p in params:
+        x = p.split(&quot;=&quot;)
+        if len(x) == 1:
+          x.append(x[0])
+        assert 2 == len(x)
+        self.params.append(tuple(x))
+      self.processParams()
 
-  def __init__(self):
-    return
-  
-  def isReadOnly(self):
-    return True
+  def dump(self):
+    print &quot;dev=%s path=%s filesystem=%s param=%s&quot; % \
+      (self.dev, self.path, self.filesystem, self.params)
 
-  def isNoExec(self):
-    return True
+  def processParams(self):
+    for p in self.params:
+      k,v = p
+      if &quot;rw&quot; == k:
+        self.isreadonly = False
+      elif &quot;ro&quot; == k:
+        self.isreadonly = True
+      elif &quot;noexec&quot; == k:
+        self.isnoexec = True
+      print &quot;%s=%s&quot; % (k,v)
 
+# whoops, python doesn't have proper static methods; bummer!
+# &lt;URL: http://code.activestate.com/recipes/52304/&gt;
+# This is easy to solve with a simple tiny wrapper:
+class Callable:
+  def __init__(self, anycallable):
+    self.__call__ = anycallable
 
 class Path:
-  abspath   = &quot;&quot;
-  dir       = &quot;&quot;
-  component = &quot;&quot;
-  symlink   = &quot;&quot;          # path this file points to if symlink
-  uid       = User.NONE
-  gid       = Group.NONE
-  status    = Perm()
-  mode      = 0 # ???
-  mntpt     = MountPoint()
-
-  def isSymlink(self):
-    pass
-
-  def isDir(self):
-    pass
+  cwd = os.getcwd()
+  def init(self, path):
+    self.origpath  = path         # 
+    self.abspath   = &quot;&quot;           # 
+    self.dir       = &quot;&quot;           # 
+    self.component = &quot;&quot;           # 
+    self.symlink   = &quot;&quot;           # path this file points to if symlink
+    self.uid       = User.NONE    # 
+    self.gid       = Group.NONE   # 
+    self.status    = Perm()       # 
+    self.mode      = 0            # ???
+    self.mntpt     = None         # 
+    self.issymlink = False        # 
+    self.isdir     = False        # 
+    self.isfile    = False        # 
+    self.issticky  = False        # 
+    self.ismntpt   = False        # 
 
-  def isFile(self):
-    pass
+  def __init__(self, path):
+    self.init(path)
 
-  def isSticky(self):
-    pass
+  # normalize the given path, which may be relative, to an absolute path
+  def normalize(path, cwd):
+    normal = &quot;&quot;
+    if len(path) &gt; 0:
+      pieces = path.split(&quot;/&quot;)
+      # remove empty components, i.e. &quot;//&quot; -&gt; &quot;/&quot;
+      pieces = filter(lambda x: &quot;&quot; != x, pieces)
+      # remove self-referential &quot;.&quot; components
+      pieces = filter(lambda x: &quot;.&quot; != x, pieces)
+      # interpolate &quot;..&quot;
+      i = 0
+      while i &lt; len(pieces):
+        if &quot;..&quot; != pieces[i]:
+          i = i + 1
+        else:
+          pieces.remove(pieces[i])
+          if i &gt; 0:
+            pieces.remove(pieces[i-1])
+      # all done, re-join
+      normal = &quot;/&quot;.join(pieces)
+      if &quot;/&quot; == path[0:1]:
+        # path is absolute
+        normal = &quot;/&quot; + normal
+      else:
+        # path is relative, use cwd
+        if len(cwd) &gt; 0 and &quot;/&quot; != cwd[-1]:
+          normal = &quot;/&quot; + normal
+        normal = cwd + normal
+    return normal
+  # sneaky trick to make normalize a static-like method
+  normalize = Callable(normalize)
 
-  def isMountPoint(self):
-    pass
+  # calculate property values
+  def calc(self):
+    try:
+      st = os.lstat(&quot;/&quot;)
+    except OSError:
+      err
+    mode = st[stat.ST_MODE]
+    uid  = st[stat.ST_UID]
+    gid  = st[stat.ST_GID]
 
   def StatusNotOK(self):
     pass
 
-
 print &quot;cwd = %s&quot; % os.getcwd()
 print &quot;uid = %d&quot; % os.getuid()
 
+# build dictionary of all system mountpoints
+MountPoints = { }
+fin, fout = popen2.popen2(&quot;mount&quot;)
+for line in fin:
+  m = MountPoint(line.strip())
+  m.dump()
+  print &quot;m.path=%s&quot; % m.path
+  MountPoints[m.path] = m
+fin.close()
+
+# path normalization unit test
+PathTest = [
+  # handle empty
+  (&quot;&quot;,        &quot;&quot;),
+  # merge duplicate path separators
+  (&quot;/&quot;,       &quot;/&quot;),
+  (&quot;//&quot;,      &quot;/&quot;),
+  (&quot;///&quot;,     &quot;/&quot;),
+  # remove &quot;.&quot;
+  (&quot;.&quot;,       &quot;&quot;),
+  (&quot;/.&quot;,      &quot;/&quot;),
+  (&quot;/./&quot;,     &quot;/&quot;),
+  (&quot;/./.&quot;,    &quot;/&quot;),
+  # handle &quot;..&quot;
+  (&quot;..&quot;,      &quot;&quot;),
+  (&quot;/..&quot;,     &quot;/&quot;),
+  (&quot;/../..&quot;,  &quot;/&quot;),
+  (&quot;/./..&quot;,   &quot;/&quot;),
+  (&quot;/../.&quot;,   &quot;/&quot;),
+  # handle spaces
+  (&quot;/ /&quot;,     &quot;/ &quot;),
+  # various
+  (&quot;/a/b/c&quot;,  &quot;/a/b/c&quot;),
+  (&quot;/a/b/c/&quot;, &quot;/a/b/c&quot;),
+  (&quot;/a/./b&quot;,  &quot;/a/b&quot;),
+  (&quot;/a/../b&quot;, &quot;/b&quot;),
+  (&quot;/../b&quot;,   &quot;/b&quot;),
+  # account for cwd
+]
+for pt in PathTest:
+  before,after = pt
+  actual = Path.normalize(before, &quot;&quot;)
+  print &quot;checking %-7s -&gt; %-7s (%s)...&quot; % (before, after, actual)
+  assert after == actual
+
+st   = os.lstat(&quot;/&quot;)
+mode = st[stat.ST_MODE]
+uid  = st[stat.ST_UID]
+gid  = st[stat.ST_GID]
+
+print &quot;mode=%08x uid=%d gid=%d&quot; % (mode,uid,gid)
 </diff>
      <filename>src/shac.py</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>29923c712e5b7ad64558ac59dd7b9ee6c1640301</id>
    </parent>
  </parents>
  <author>
    <name>Ryan Flynn</name>
    <email>parseerror@gmail.com</email>
  </author>
  <url>http://github.com/pizza/shac/commit/e717192288951ece9ae83a98efd462dd67674555</url>
  <id>e717192288951ece9ae83a98efd462dd67674555</id>
  <committed-date>2009-04-21T01:17:07-07:00</committed-date>
  <authored-date>2009-04-21T01:17:07-07:00</authored-date>
  <message>replaced 'class attributes' with instance attributes; added path normalization and some testing</message>
  <tree>ce8a39b68c45a933a6afed821007edb4b4203f00</tree>
  <committer>
    <name>Ryan Flynn</name>
    <email>parseerror@gmail.com</email>
  </committer>
</commit>
