2 Miscellaneous utility functions and classes.
6 from wizard.util import *
18 class ChangeDirectory(object):
20 Context for temporarily changing the working directory.
22 >>> with ChangeDirectory("/tmp"):
26 def __init__(self, dir):
30 self.olddir = os.getcwd()
32 def __exit__(self, *args):
35 class Counter(object):
37 Object for counting different values when you don't know what
38 they are a priori. Supports index access and iteration.
40 >>> counter = Counter()
41 >>> counter.count("foo")
42 >>> print counter["foo"]
47 def count(self, value):
48 """Increments count for ``value``."""
49 self.dict.setdefault(value, 0)
51 def __getitem__(self, key):
54 return self.dict.__iter__()
56 class PipeToLess(object):
58 Context for printing output to a pager. Use this if output
59 is expected to be long.
62 self.proc = subprocess.Popen("less", stdin=subprocess.PIPE)
63 self.old_stdout = sys.stdout
64 sys.stdout = self.proc.stdin
65 def __exit__(self, *args):
67 self.proc.stdin.close()
69 sys.stdout = self.old_stdout
73 A map function for dictionaries. Only changes values.
75 >>> dictmap(lambda x: x + 2, {'a': 1, 'b': 2})
78 return dict((k,f(v)) for k,v in d.items())
82 A map function for dictionaries that passes key and value.
84 >>> dictkmap(lambda x, y: x + y, {1: 4, 3: 4})
87 return dict((k,f(k,v)) for k,v in d.items())
89 def get_exception_name(output):
91 Reads the traceback from a Python program and grabs the
92 fully qualified exception name.
94 lines = output.split("\n")
96 for line in lines[1:]:
110 def get_dir_uid(dir):
111 """Finds the uid of the person who owns this directory."""
112 return os.stat(dir).st_uid
114 def get_dir_owner(dir = "."):
116 Finds the name of the locker this directory is in.
120 This function uses the passwd database and thus
121 only works on scripts servers when querying directories
124 pwentry = pwd.getpwuid(get_dir_uid(dir))
125 # XXX: Error handling!
126 return pwentry.pw_name
129 """Returns the commit ID of the current Wizard install."""
130 # If you decide to convert this to use wizard.shell, be warned
131 # that there is a circular dependency, so this function would
132 # probably have to live somewhere else, probably wizard.git
133 wizard_git = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), ".git")
134 return subprocess.Popen(["git", "--git-dir=" + wizard_git, "rev-parse", "HEAD"], stdout=subprocess.PIPE).communicate()[0].rstrip()
136 def get_operator_info():
138 Returns tuple of ``(realname, email)`` about the person running
139 the script. If run from a scripts server, get info from Hesiod.
140 Otherwise, use the passwd database (email generated probably won't
141 actually accept mail). Useful when generating commit messages.
143 username = get_operator_name_from_gssapi()
146 hesinfo = subprocess.Popen(["hesinfo", username, "passwd"],stdout=subprocess.PIPE).communicate()[0]
147 fields = hesinfo.partition(",")[0]
148 realname = fields.rpartition(":")[2]
149 return realname, username + "@mit.edu"
151 # more traditional approach, but the email probably doesn't work
154 # since root isn't actually a useful designation, but maybe
155 # SUDO_USER contains something helpful
156 sudo_user = os.getenv("SUDO_USER")
159 pwdentry = pwd.getpwnam(sudo_user)
161 pwdentry = pwd.getpwuid(uid)
162 # XXX: error checking might be nice
163 # We follow the Ubuntu convention of gecos being a comma split field
164 # with the person's realname being the first entry.
165 return pwdentry.pw_gecos.split(",")[0], pwdentry.pw_name + "@" + socket.gethostname()
167 def get_operator_git():
169 Returns ``Real Name <username@mit.edu>`` suitable for use in
170 Git ``Something-by:`` string.
172 return "%s <%s>" % get_operator_info()
174 def get_operator_name_from_gssapi():
176 Returns username of the person operating this script based
177 off of the :envvar:`SSH_GSSAPI_NAME` environment variable.
181 :envvar:`SSH_GSSAPI_NAME` is not set by a vanilla OpenSSH
182 distributions. Scripts servers are patched to support this
183 environment variable.
185 principal = os.getenv("SSH_GSSAPI_NAME")
188 instance, _, _ = principal.partition("@")
189 if instance.endswith("/root"):
190 username, _, _ = principal.partition("/")
195 def set_operator_env():
197 Sets :envvar:`GIT_COMMITTER_NAME` and :envvar:`GIT_COMMITTER_EMAIL`
198 environment variables if applicable. Does nothing if
199 :func:`get_operator_info` throws :exc:`NoOperatorInfo`.
202 op_realname, op_email = get_operator_info()
203 os.putenv("GIT_COMMITTER_NAME", op_realname)
204 os.putenv("GIT_COMMITTER_EMAIL", op_email)
205 except NoOperatorInfo:
208 def set_author_env():
210 Sets :envvar:`GIT_AUTHOR_NAME` and :envvar:`GIT_AUTHOR_EMAIL` environment
211 variables if applicable. Does nothing if :func:`get_dir_owner` fails.
214 # XXX: should check if the directory is in AFS, and if not, use
215 # a more traditional metric
216 lockername = get_dir_owner()
217 os.putenv("GIT_AUTHOR_NAME", "%s locker" % lockername)
218 os.putenv("GIT_AUTHOR_EMAIL", "%s@scripts.mit.edu" % lockername)
219 except KeyError: # XXX: This doesn't actually make sense
223 """Sets all appropriate environment variables for Git commits."""
227 def get_git_footer():
228 """Returns strings for placing in Git log info about Wizard."""
229 return "\n".join(["Wizard-revision: %s" % get_revision()
230 ,"Wizard-args: %s" % " ".join(sys.argv)
233 class NoOperatorInfo(wizard.Error):
234 """No information could be found about the operator from Kerberos."""