root/trunk/twisted/python/util.py

Revision 29907, 29.0 KB (checked in by mithrandi, 4 weeks ago)

Reopens #4536

The tests don't work on Python 2.4.

Line 
1# -*- test-case-name: twisted.python.test.test_util -*-
2# Copyright (c) 2001-2010 Twisted Matrix Laboratories.
3# See LICENSE for details.
4
5import os, sys, hmac, errno, new, inspect, warnings
6try:
7    import pwd, grp
8except ImportError:
9    pwd = grp = None
10try:
11    from os import setgroups, getgroups
12except ImportError:
13    setgroups = getgroups = None
14from UserDict import UserDict
15
16
17class InsensitiveDict:
18    """Dictionary, that has case-insensitive keys.
19
20    Normally keys are retained in their original form when queried with
21    .keys() or .items().  If initialized with preserveCase=0, keys are both
22    looked up in lowercase and returned in lowercase by .keys() and .items().
23    """
24    """
25    Modified recipe at
26    http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66315 originally
27    contributed by Sami Hangaslammi.
28    """
29
30    def __init__(self, dict=None, preserve=1):
31        """Create an empty dictionary, or update from 'dict'."""
32        self.data = {}
33        self.preserve=preserve
34        if dict:
35            self.update(dict)
36
37    def __delitem__(self, key):
38        k=self._lowerOrReturn(key)
39        del self.data[k]
40
41    def _lowerOrReturn(self, key):
42        if isinstance(key, str) or isinstance(key, unicode):
43            return key.lower()
44        else:
45            return key
46
47    def __getitem__(self, key):
48        """Retrieve the value associated with 'key' (in any case)."""
49        k = self._lowerOrReturn(key)
50        return self.data[k][1]
51
52    def __setitem__(self, key, value):
53        """Associate 'value' with 'key'. If 'key' already exists, but
54        in different case, it will be replaced."""
55        k = self._lowerOrReturn(key)
56        self.data[k] = (key, value)
57
58    def has_key(self, key):
59        """Case insensitive test whether 'key' exists."""
60        k = self._lowerOrReturn(key)
61        return self.data.has_key(k)
62    __contains__=has_key
63
64    def _doPreserve(self, key):
65        if not self.preserve and (isinstance(key, str)
66                                  or isinstance(key, unicode)):
67            return key.lower()
68        else:
69            return key
70
71    def keys(self):
72        """List of keys in their original case."""
73        return list(self.iterkeys())
74
75    def values(self):
76        """List of values."""
77        return list(self.itervalues())
78
79    def items(self):
80        """List of (key,value) pairs."""
81        return list(self.iteritems())
82
83    def get(self, key, default=None):
84        """Retrieve value associated with 'key' or return default value
85        if 'key' doesn't exist."""
86        try:
87            return self[key]
88        except KeyError:
89            return default
90
91    def setdefault(self, key, default):
92        """If 'key' doesn't exists, associate it with the 'default' value.
93        Return value associated with 'key'."""
94        if not self.has_key(key):
95            self[key] = default
96        return self[key]
97
98    def update(self, dict):
99        """Copy (key,value) pairs from 'dict'."""
100        for k,v in dict.items():
101            self[k] = v
102
103    def __repr__(self):
104        """String representation of the dictionary."""
105        items = ", ".join([("%r: %r" % (k,v)) for k,v in self.items()])
106        return "InsensitiveDict({%s})" % items
107
108    def iterkeys(self):
109        for v in self.data.itervalues():
110            yield self._doPreserve(v[0])
111
112    def itervalues(self):
113        for v in self.data.itervalues():
114            yield v[1]
115
116    def iteritems(self):
117        for (k, v) in self.data.itervalues():
118            yield self._doPreserve(k), v
119
120    def popitem(self):
121        i=self.items()[0]
122        del self[i[0]]
123        return i
124
125    def clear(self):
126        for k in self.keys():
127            del self[k]
128
129    def copy(self):
130        return InsensitiveDict(self, self.preserve)
131
132    def __len__(self):
133        return len(self.data)
134
135    def __eq__(self, other):
136        for k,v in self.items():
137            if not (k in other) or not (other[k]==v):
138                return 0
139        return len(self)==len(other)
140
141class OrderedDict(UserDict):
142    """A UserDict that preserves insert order whenever possible."""
143    def __init__(self, dict=None, **kwargs):
144        self._order = []
145        self.data = {}
146        if dict is not None:
147            if hasattr(dict,'keys'):
148                self.update(dict)
149            else:
150                for k,v in dict: # sequence
151                    self[k] = v
152        if len(kwargs):
153            self.update(kwargs)
154    def __repr__(self):
155        return '{'+', '.join([('%r: %r' % item) for item in self.items()])+'}'
156
157    def __setitem__(self, key, value):
158        if not self.has_key(key):
159            self._order.append(key)
160        UserDict.__setitem__(self, key, value)
161
162    def copy(self):
163        return self.__class__(self)
164
165    def __delitem__(self, key):
166        UserDict.__delitem__(self, key)
167        self._order.remove(key)
168
169    def iteritems(self):
170        for item in self._order:
171            yield (item, self[item])
172
173    def items(self):
174        return list(self.iteritems())
175
176    def itervalues(self):
177        for item in self._order:
178            yield self[item]
179
180    def values(self):
181        return list(self.itervalues())
182
183    def iterkeys(self):
184        return iter(self._order)
185
186    def keys(self):
187        return list(self._order)
188
189    def popitem(self):
190        key = self._order[-1]
191        value = self[key]
192        del self[key]
193        return (key, value)
194
195    def setdefault(self, item, default):
196        if self.has_key(item):
197            return self[item]
198        self[item] = default
199        return default
200
201    def update(self, d):
202        for k, v in d.items():
203            self[k] = v
204
205def uniquify(lst):
206    """Make the elements of a list unique by inserting them into a dictionary.
207    This must not change the order of the input lst.
208    """
209    dct = {}
210    result = []
211    for k in lst:
212        if not dct.has_key(k): result.append(k)
213        dct[k] = 1
214    return result
215
216def padTo(n, seq, default=None):
217    """Pads a sequence out to n elements,
218
219    filling in with a default value if it is not long enough.
220
221    If the input sequence is longer than n, raises ValueError.
222
223    Details, details:
224    This returns a new list; it does not extend the original sequence.
225    The new list contains the values of the original sequence, not copies.
226    """
227
228    if len(seq) > n:
229        raise ValueError, "%d elements is more than %d." % (len(seq), n)
230
231    blank = [default] * n
232
233    blank[:len(seq)] = list(seq)
234
235    return blank
236
237def getPluginDirs():
238    import twisted
239    systemPlugins = os.path.join(os.path.dirname(os.path.dirname(
240                            os.path.abspath(twisted.__file__))), 'plugins')
241    userPlugins = os.path.expanduser("~/TwistedPlugins")
242    confPlugins = os.path.expanduser("~/.twisted")
243    allPlugins = filter(os.path.isdir, [systemPlugins, userPlugins, confPlugins])
244    return allPlugins
245
246def addPluginDir():
247    sys.path.extend(getPluginDirs())
248
249def sibpath(path, sibling):
250    """Return the path to a sibling of a file in the filesystem.
251
252    This is useful in conjunction with the special __file__ attribute
253    that Python provides for modules, so modules can load associated
254    resource files.
255    """
256    return os.path.join(os.path.dirname(os.path.abspath(path)), sibling)
257
258
259def _getpass(prompt):
260    """Helper to turn IOErrors into KeyboardInterrupts"""
261    import getpass
262    try:
263        return getpass.getpass(prompt)
264    except IOError, e:
265        if e.errno == errno.EINTR:
266            raise KeyboardInterrupt
267        raise
268    except EOFError:
269        raise KeyboardInterrupt
270
271def getPassword(prompt = 'Password: ', confirm = 0, forceTTY = 0,
272                confirmPrompt = 'Confirm password: ',
273                mismatchMessage = "Passwords don't match."):
274    """Obtain a password by prompting or from stdin.
275
276    If stdin is a terminal, prompt for a new password, and confirm (if
277    C{confirm} is true) by asking again to make sure the user typed the same
278    thing, as keystrokes will not be echoed.
279
280    If stdin is not a terminal, and C{forceTTY} is not true, read in a line
281    and use it as the password, less the trailing newline, if any.  If
282    C{forceTTY} is true, attempt to open a tty and prompt for the password
283    using it.  Raise a RuntimeError if this is not possible.
284
285    @returns: C{str}
286    """
287    isaTTY = hasattr(sys.stdin, 'isatty') and sys.stdin.isatty()
288
289    old = None
290    try:
291        if not isaTTY:
292            if forceTTY:
293                try:
294                    old = sys.stdin, sys.stdout
295                    sys.stdin = sys.stdout = open('/dev/tty', 'r+')
296                except:
297                    raise RuntimeError("Cannot obtain a TTY")
298            else:
299                password = sys.stdin.readline()
300                if password[-1] == '\n':
301                    password = password[:-1]
302                return password
303
304        while 1:
305            try1 = _getpass(prompt)
306            if not confirm:
307                return try1
308            try2 = _getpass(confirmPrompt)
309            if try1 == try2:
310                return try1
311            else:
312                sys.stderr.write(mismatchMessage + "\n")
313    finally:
314        if old:
315            sys.stdin.close()
316            sys.stdin, sys.stdout = old
317
318
319def dict(*a, **k):
320    import __builtin__
321    warnings.warn('twisted.python.util.dict is deprecated.  Use __builtin__.dict instead')
322    return __builtin__.dict(*a, **k)
323
324def println(*a):
325    sys.stdout.write(' '.join(map(str, a))+'\n')
326
327# XXX
328# This does not belong here
329# But where does it belong?
330
331def str_xor(s, b):
332    return ''.join([chr(ord(c) ^ b) for c in s])
333
334def keyed_md5(secret, challenge):
335    """
336    Create the keyed MD5 string for the given secret and challenge.
337    """
338    warnings.warn(
339        "keyed_md5() is deprecated.  Use the stdlib module hmac instead.",
340        DeprecationWarning, stacklevel=2
341    )
342    return hmac.HMAC(secret, challenge).hexdigest()
343
344def makeStatBar(width, maxPosition, doneChar = '=', undoneChar = '-', currentChar = '>'):
345    """Creates a function that will return a string representing a progress bar.
346    """
347    aValue = width / float(maxPosition)
348    def statBar(position, force = 0, last = ['']):
349        assert len(last) == 1, "Don't mess with the last parameter."
350        done = int(aValue * position)
351        toDo = width - done - 2
352        result = "[%s%s%s]" % (doneChar * done, currentChar, undoneChar * toDo)
353        if force:
354            last[0] = result
355            return result
356        if result == last[0]:
357            return ''
358        last[0] = result
359        return result
360
361    statBar.__doc__ = """statBar(position, force = 0) -> '[%s%s%s]'-style progress bar
362
363    returned string is %d characters long, and the range goes from 0..%d.
364    The 'position' argument is where the '%s' will be drawn.  If force is false,
365    '' will be returned instead if the resulting progress bar is identical to the
366    previously returned progress bar.
367""" % (doneChar * 3, currentChar, undoneChar * 3, width, maxPosition, currentChar)
368    return statBar
369
370def spewer(frame, s, ignored):
371    """A trace function for sys.settrace that prints every function or method call."""
372    from twisted.python import reflect
373    if frame.f_locals.has_key('self'):
374        se = frame.f_locals['self']
375        if hasattr(se, '__class__'):
376            k = reflect.qual(se.__class__)
377        else:
378            k = reflect.qual(type(se))
379        print 'method %s of %s at %s' % (
380            frame.f_code.co_name, k, id(se)
381        )
382    else:
383        print 'function %s in %s, line %s' % (
384            frame.f_code.co_name,
385            frame.f_code.co_filename,
386            frame.f_lineno)
387
388def searchupwards(start, files=[], dirs=[]):
389    """Walk upwards from start, looking for a directory containing
390    all files and directories given as arguments::
391    >>> searchupwards('.', ['foo.txt'], ['bar', 'bam'])
392
393    If not found, return None
394    """
395    start=os.path.abspath(start)
396    parents=start.split(os.sep)
397    exists=os.path.exists; join=os.sep.join; isdir=os.path.isdir
398    while len(parents):
399        candidate=join(parents)+os.sep
400        allpresent=1
401        for f in files:
402            if not exists("%s%s" % (candidate, f)):
403                allpresent=0
404                break
405        if allpresent:
406            for d in dirs:
407                if not isdir("%s%s" % (candidate, d)):
408                    allpresent=0
409                    break
410        if allpresent: return candidate
411        parents.pop(-1)
412    return None
413
414
415class LineLog:
416    """
417    A limited-size line-based log, useful for logging line-based
418    protocols such as SMTP.
419
420    When the log fills up, old entries drop off the end.
421    """
422    def __init__(self, size=10):
423        """
424        Create a new log, with size lines of storage (default 10).
425        A log size of 0 (or less) means an infinite log.
426        """
427        if size < 0:
428            size = 0
429        self.log = [None]*size
430        self.size = size
431
432    def append(self,line):
433        if self.size:
434            self.log[:-1] = self.log[1:]
435            self.log[-1] = line
436        else:
437            self.log.append(line)
438
439    def str(self):
440        return '\n'.join(filter(None,self.log))
441
442    def __getitem__(self, item):
443        return filter(None,self.log)[item]
444
445    def clear(self):
446        """Empty the log"""
447        self.log = [None]*self.size
448
449def raises(exception, f, *args, **kwargs):
450    """Determine whether the given call raises the given exception"""
451    try:
452        f(*args, **kwargs)
453    except exception:
454        return 1
455    return 0
456
457class IntervalDifferential:
458    """
459    Given a list of intervals, generate the amount of time to sleep between
460    \"instants\".
461
462    For example, given 7, 11 and 13, the three (infinite) sequences::
463
464        7 14 21 28 35 ...
465        11 22 33 44 ...
466        13 26 39 52 ...
467
468    will be generated, merged, and used to produce::
469
470        (7, 0) (4, 1) (2, 2) (1, 0) (7, 0) (1, 1) (4, 2) (2, 0) (5, 1) (2, 0)
471
472    New intervals may be added or removed as iteration proceeds using the
473    proper methods.
474    """
475
476    def __init__(self, intervals, default=60):
477        """
478        @type intervals: C{list} of C{int}, C{long}, or C{float} param
479        @param intervals: The intervals between instants.
480
481        @type default: C{int}, C{long}, or C{float}
482        @param default: The duration to generate if the intervals list
483        becomes empty.
484        """
485        self.intervals = intervals[:]
486        self.default = default
487
488    def __iter__(self):
489        return _IntervalDifferentialIterator(self.intervals, self.default)
490
491class _IntervalDifferentialIterator:
492    def __init__(self, i, d):
493
494        self.intervals = [[e, e, n] for (e, n) in zip(i, range(len(i)))]
495        self.default = d
496        self.last = 0
497
498    def next(self):
499        if not self.intervals:
500            return (self.default, None)
501        last, index = self.intervals[0][0], self.intervals[0][2]
502        self.intervals[0][0] += self.intervals[0][1]
503        self.intervals.sort()
504        result = last - self.last
505        self.last = last
506        return result, index
507
508    def addInterval(self, i):
509        if self.intervals:
510            delay = self.intervals[0][0] - self.intervals[0][1]
511            self.intervals.append([delay + i, i, len(self.intervals)])
512            self.intervals.sort()
513        else:
514            self.intervals.append([i, i, 0])
515
516    def removeInterval(self, interval):
517        for i in range(len(self.intervals)):
518            if self.intervals[i][1] == interval:
519                index = self.intervals[i][2]
520                del self.intervals[i]
521                for i in self.intervals:
522                    if i[2] > index:
523                        i[2] -= 1
524                return
525        raise ValueError, "Specified interval not in IntervalDifferential"
526
527
528class FancyStrMixin:
529    """
530    Set showAttributes to a sequence of strings naming attributes, OR
531    sequences of (attributeName, displayName, formatCharacter)
532    """
533    showAttributes = ()
534    def __str__(self):
535        r = ['<', hasattr(self, 'fancybasename') and self.fancybasename or self.__class__.__name__]
536        for attr in self.showAttributes:
537            if isinstance(attr, str):
538                r.append(' %s=%r' % (attr, getattr(self, attr)))
539            else:
540                r.append((' %s=' + attr[2]) % (attr[1], getattr(self, attr[0])))
541        r.append('>')
542        return ''.join(r)
543    __repr__ = __str__
544
545
546
547class FancyEqMixin:
548    compareAttributes = ()
549    def __eq__(self, other):
550        if not self.compareAttributes:
551            return self is other
552        if isinstance(self, other.__class__):
553            return (
554                [getattr(self, name) for name in self.compareAttributes] ==
555                [getattr(other, name) for name in self.compareAttributes])
556        return NotImplemented
557
558
559    def __ne__(self, other):
560        result = self.__eq__(other)
561        if result is NotImplemented:
562            return result
563        return not result
564
565
566
567def dsu(list, key):
568    """
569    decorate-sort-undecorate (aka "Schwartzian transform")
570
571    DEPRECATED. Use the built-in C{sorted()} instead.
572    """
573    warnings.warn(("dsu is deprecated since Twisted 10.1. "
574        "Use the built-in sorted() instead."), DeprecationWarning, stacklevel=2)
575    L2 = [(key(e), i, e) for (i, e) in zip(range(len(list)), list)]
576    L2.sort()
577    return [e[2] for e in L2]
578
579
580try:
581    from twisted.python._initgroups import initgroups as _c_initgroups
582except ImportError:
583    _c_initgroups = None
584
585
586
587if pwd is None or grp is None or setgroups is None or getgroups is None:
588    def initgroups(uid, primaryGid):
589        """
590        Do nothing.
591
592        Underlying platform support require to manipulate groups is missing.
593        """
594else:
595    # Fallback to the inefficient Python version
596    def _setgroups_until_success(l):
597        while(1):
598            # NASTY NASTY HACK (but glibc does it so it must be okay):
599            # In case sysconfig didn't give the right answer, find the limit
600            # on max groups by just looping, trying to set fewer and fewer
601            # groups each time until it succeeds.
602            try:
603                setgroups(l)
604            except ValueError:
605                # This exception comes from python itself restricting
606                # number of groups allowed.
607                if len(l) > 1:
608                    del l[-1]
609                else:
610                    raise
611            except OSError, e:
612                if e.errno == errno.EINVAL and len(l) > 1:
613                    # This comes from the OS saying too many groups
614                    del l[-1]
615                else:
616                    raise
617            else:
618                # Success, yay!
619                return
620
621    def initgroups(uid, primaryGid):
622        """
623        Initializes the group access list.
624
625        If the C extension is present, we're calling it, which in turn calls
626        initgroups(3).
627       
628        If not, this is done by reading the group database /etc/group and using
629        all groups of which C{uid} is a member.  The additional group
630        C{primaryGid} is also added to the list.
631
632        If the given user is a member of more than C{NGROUPS}, arbitrary
633        groups will be silently discarded to bring the number below that
634        limit.
635
636        @type uid: C{int}
637        @param uid: The UID for which to look up group information.
638
639        @type primaryGid: C{int} or C{NoneType}
640        @param primaryGid: If provided, an additional GID to include when
641            setting the groups.
642        """
643        if _c_initgroups is not None:
644            return _c_initgroups(pwd.getpwuid(uid)[0], primaryGid)
645        try:
646            # Try to get the maximum number of groups
647            max_groups = os.sysconf("SC_NGROUPS_MAX")
648        except:
649            # No predefined limit
650            max_groups = 0
651
652        username = pwd.getpwuid(uid)[0]
653        l = []
654        if primaryGid is not None:
655            l.append(primaryGid)
656        for groupname, password, gid, userlist in grp.getgrall():
657            if username in userlist:
658                l.append(gid)
659                if len(l) == max_groups:
660                    break # No more groups, ignore any more
661        try:
662            _setgroups_until_success(l)
663        except OSError, e:
664            # We might be able to remove this code now that we
665            # don't try to setgid/setuid even when not asked to.
666            if e.errno == errno.EPERM:
667                for g in getgroups():
668                    if g not in l:
669                        raise
670            else:
671                raise
672
673
674
675def switchUID(uid, gid, euid=False):
676    if euid:
677        setuid = os.seteuid
678        setgid = os.setegid
679    else:
680        setuid = os.setuid
681        setgid = os.setgid
682    if gid is not None:
683        setgid(gid)
684    if uid is not None:
685        initgroups(uid, gid)
686        setuid(uid)
687
688
689class SubclassableCStringIO(object):
690    """A wrapper around cStringIO to allow for subclassing"""
691    __csio = None
692
693    def __init__(self, *a, **kw):
694        from cStringIO import StringIO
695        self.__csio = StringIO(*a, **kw)
696
697    def __iter__(self):
698        return self.__csio.__iter__()
699
700    def next(self):
701        return self.__csio.next()
702
703    def close(self):
704        return self.__csio.close()
705
706    def isatty(self):
707        return self.__csio.isatty()
708
709    def seek(self, pos, mode=0):
710        return self.__csio.seek(pos, mode)
711
712    def tell(self):
713        return self.__csio.tell()
714
715    def read(self, n=-1):
716        return self.__csio.read(n)
717
718    def readline(self, length=None):
719        return self.__csio.readline(length)
720
721    def readlines(self, sizehint=0):
722        return self.__csio.readlines(sizehint)
723
724    def truncate(self, size=None):
725        return self.__csio.truncate(size)
726
727    def write(self, s):
728        return self.__csio.write(s)
729
730    def writelines(self, list):
731        return self.__csio.writelines(list)
732
733    def flush(self):
734        return self.__csio.flush()
735
736    def getvalue(self):
737        return self.__csio.getvalue()
738
739def moduleMovedForSplit(origModuleName, newModuleName, moduleDesc,
740                        projectName, projectURL, globDict):
741    """
742    No-op function; only present for backwards compatibility.  There is no
743    reason to call this function.
744    """
745    warnings.warn(
746        "moduleMovedForSplit is deprecated since Twisted 9.0.",
747        DeprecationWarning, stacklevel=2)
748
749
750def untilConcludes(f, *a, **kw):
751    while True:
752        try:
753            return f(*a, **kw)
754        except (IOError, OSError), e:
755            if e.args[0] == errno.EINTR:
756                continue
757            raise
758
759_idFunction = id
760
761def setIDFunction(idFunction):
762    """
763    Change the function used by L{unsignedID} to determine the integer id value
764    of an object.  This is largely useful for testing to give L{unsignedID}
765    deterministic, easily-controlled behavior.
766
767    @param idFunction: A function with the signature of L{id}.
768    @return: The previous function being used by L{unsignedID}.
769    """
770    global _idFunction
771    oldIDFunction = _idFunction
772    _idFunction = idFunction
773    return oldIDFunction
774
775
776# A value about twice as large as any Python int, to which negative values
777# from id() will be added, moving them into a range which should begin just
778# above where positive values from id() leave off.
779_HUGEINT = (sys.maxint + 1L) * 2L
780def unsignedID(obj):
781    """
782    Return the id of an object as an unsigned number so that its hex
783    representation makes sense.
784
785    This is mostly necessary in Python 2.4 which implements L{id} to sometimes
786    return a negative value.  Python 2.3 shares this behavior, but also
787    implements hex and the %x format specifier to represent negative values as
788    though they were positive ones, obscuring the behavior of L{id}.  Python
789    2.5's implementation of L{id} always returns positive values.
790    """
791    rval = _idFunction(obj)
792    if rval < 0:
793        rval += _HUGEINT
794    return rval
795
796
797def mergeFunctionMetadata(f, g):
798    """
799    Overwrite C{g}'s name and docstring with values from C{f}.  Update
800    C{g}'s instance dictionary with C{f}'s.
801
802    To use this function safely you must use the return value. In Python 2.3,
803    L{mergeFunctionMetadata} will create a new function. In later versions of
804    Python, C{g} will be mutated and returned.
805
806    @return: A function that has C{g}'s behavior and metadata merged from
807        C{f}.
808    """
809    try:
810        g.__name__ = f.__name__
811    except TypeError:
812        try:
813            merged = new.function(
814                g.func_code, g.func_globals,
815                f.__name__, inspect.getargspec(g)[-1],
816                g.func_closure)
817        except TypeError:
818            pass
819    else:
820        merged = g
821    try:
822        merged.__doc__ = f.__doc__
823    except (TypeError, AttributeError):
824        pass
825    try:
826        merged.__dict__.update(g.__dict__)
827        merged.__dict__.update(f.__dict__)
828    except (TypeError, AttributeError):
829        pass
830    merged.__module__ = f.__module__
831    return merged
832
833
834def nameToLabel(mname):
835    """
836    Convert a string like a variable name into a slightly more human-friendly
837    string with spaces and capitalized letters.
838
839    @type mname: C{str}
840    @param mname: The name to convert to a label.  This must be a string
841    which could be used as a Python identifier.  Strings which do not take
842    this form will result in unpredictable behavior.
843
844    @rtype: C{str}
845    """
846    labelList = []
847    word = ''
848    lastWasUpper = False
849    for letter in mname:
850        if letter.isupper() == lastWasUpper:
851            # Continuing a word.
852            word += letter
853        else:
854            # breaking a word OR beginning a word
855            if lastWasUpper:
856                # could be either
857                if len(word) == 1:
858                    # keep going
859                    word += letter
860                else:
861                    # acronym
862                    # we're processing the lowercase letter after the acronym-then-capital
863                    lastWord = word[:-1]
864                    firstLetter = word[-1]
865                    labelList.append(lastWord)
866                    word = firstLetter + letter
867            else:
868                # definitely breaking: lower to upper
869                labelList.append(word)
870                word = letter
871        lastWasUpper = letter.isupper()
872    if labelList:
873        labelList[0] = labelList[0].capitalize()
874    else:
875        return mname.capitalize()
876    labelList.append(word)
877    return ' '.join(labelList)
878
879
880
881def uidFromString(uidString):
882    """
883    Convert a user identifier, as a string, into an integer UID.
884
885    @type uid: C{str}
886    @param uid: A string giving the base-ten representation of a UID or the
887        name of a user which can be converted to a UID via L{pwd.getpwnam}.
888
889    @rtype: C{int}
890    @return: The integer UID corresponding to the given string.
891
892    @raise ValueError: If the user name is supplied and L{pwd} is not
893        available.
894    """
895    try:
896        return int(uidString)
897    except ValueError:
898        if pwd is None:
899            raise
900        return pwd.getpwnam(uidString)[2]
901
902
903
904def gidFromString(gidString):
905    """
906    Convert a group identifier, as a string, into an integer GID.
907
908    @type uid: C{str}
909    @param uid: A string giving the base-ten representation of a GID or the
910        name of a group which can be converted to a GID via L{grp.getgrnam}.
911
912    @rtype: C{int}
913    @return: The integer GID corresponding to the given string.
914
915    @raise ValueError: If the group name is supplied and L{grp} is not
916        available.
917    """
918    try:
919        return int(gidString)
920    except ValueError:
921        if grp is None:
922            raise
923        return grp.getgrnam(gidString)[2]
924
925
926
927def runAsEffectiveUser(euid, egid, function, *args, **kwargs):
928    """
929    Run the given function wrapped with seteuid/setegid calls.
930
931    This will try to minimize the number of seteuid/setegid calls, comparing
932    current and wanted permissions
933
934    @param euid: effective UID used to call the function.
935    @type euid: C{int}
936
937    @type egid: effective GID used to call the function.
938    @param egid: C{int}
939
940    @param function: the function run with the specific permission.
941    @type function: any callable
942
943    @param *args: arguments passed to C{function}
944    @param **kwargs: keyword arguments passed to C{function}
945    """
946    uid, gid = os.geteuid(), os.getegid()
947    if uid == euid and gid == egid:
948        return function(*args, **kwargs)
949    else:
950        if uid != 0 and (uid != euid or gid != egid):
951            os.seteuid(0)
952        if gid != egid:
953            os.setegid(egid)
954        if euid != 0 and (euid != uid or gid != egid):
955            os.seteuid(euid)
956        try:
957            return function(*args, **kwargs)
958        finally:
959            if euid != 0 and (uid != euid or gid != egid):
960                os.seteuid(0)
961            if gid != egid:
962                os.setegid(gid)
963            if uid != 0 and (uid != euid or gid != egid):
964                os.seteuid(uid)
965
966
967
968__all__ = [
969    "uniquify", "padTo", "getPluginDirs", "addPluginDir", "sibpath",
970    "getPassword", "dict", "println", "keyed_md5", "makeStatBar",
971    "OrderedDict", "InsensitiveDict", "spewer", "searchupwards", "LineLog",
972    "raises", "IntervalDifferential", "FancyStrMixin", "FancyEqMixin",
973    "dsu", "switchUID", "SubclassableCStringIO", "moduleMovedForSplit",
974    "unsignedID", "mergeFunctionMetadata", "nameToLabel", "uidFromString",
975    "gidFromString", "runAsEffectiveUser", "moduleMovedForSplit",
976]
Note: See TracBrowser for help on using the browser.