mirror of
https://github.com/clinton-hall/nzbToMedia.git
synced 2025-08-21 13:53:15 -07:00
Move Windows libs to libs/windows
This commit is contained in:
parent
3975aaceb2
commit
3a692c94a5
684 changed files with 4 additions and 1 deletions
0
libs/win/jaraco/classes/__init__.py
Normal file
0
libs/win/jaraco/classes/__init__.py
Normal file
75
libs/win/jaraco/classes/ancestry.py
Normal file
75
libs/win/jaraco/classes/ancestry.py
Normal file
|
@ -0,0 +1,75 @@
|
|||
"""
|
||||
Routines for obtaining the class names
|
||||
of an object and its parent classes.
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
def all_bases(c):
|
||||
"""
|
||||
return a tuple of all base classes the class c has as a parent.
|
||||
>>> object in all_bases(list)
|
||||
True
|
||||
"""
|
||||
return c.mro()[1:]
|
||||
|
||||
|
||||
def all_classes(c):
|
||||
"""
|
||||
return a tuple of all classes to which c belongs
|
||||
>>> list in all_classes(list)
|
||||
True
|
||||
"""
|
||||
return c.mro()
|
||||
|
||||
# borrowed from
|
||||
# http://code.activestate.com/recipes/576949-find-all-subclasses-of-a-given-class/
|
||||
|
||||
|
||||
def iter_subclasses(cls, _seen=None):
|
||||
"""
|
||||
Generator over all subclasses of a given class, in depth-first order.
|
||||
|
||||
>>> bool in list(iter_subclasses(int))
|
||||
True
|
||||
>>> class A(object): pass
|
||||
>>> class B(A): pass
|
||||
>>> class C(A): pass
|
||||
>>> class D(B,C): pass
|
||||
>>> class E(D): pass
|
||||
>>>
|
||||
>>> for cls in iter_subclasses(A):
|
||||
... print(cls.__name__)
|
||||
B
|
||||
D
|
||||
E
|
||||
C
|
||||
>>> # get ALL (new-style) classes currently defined
|
||||
>>> res = [cls.__name__ for cls in iter_subclasses(object)]
|
||||
>>> 'type' in res
|
||||
True
|
||||
>>> 'tuple' in res
|
||||
True
|
||||
>>> len(res) > 100
|
||||
True
|
||||
"""
|
||||
|
||||
if not isinstance(cls, type):
|
||||
raise TypeError(
|
||||
'iter_subclasses must be called with '
|
||||
'new-style classes, not %.100r' % cls
|
||||
)
|
||||
if _seen is None:
|
||||
_seen = set()
|
||||
try:
|
||||
subs = cls.__subclasses__()
|
||||
except TypeError: # fails only when cls is type
|
||||
subs = cls.__subclasses__(cls)
|
||||
for sub in subs:
|
||||
if sub in _seen:
|
||||
continue
|
||||
_seen.add(sub)
|
||||
yield sub
|
||||
for sub in iter_subclasses(sub, _seen):
|
||||
yield sub
|
41
libs/win/jaraco/classes/meta.py
Normal file
41
libs/win/jaraco/classes/meta.py
Normal file
|
@ -0,0 +1,41 @@
|
|||
"""
|
||||
meta.py
|
||||
|
||||
Some useful metaclasses.
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
class LeafClassesMeta(type):
|
||||
"""
|
||||
A metaclass for classes that keeps track of all of them that
|
||||
aren't base classes.
|
||||
"""
|
||||
|
||||
_leaf_classes = set()
|
||||
|
||||
def __init__(cls, name, bases, attrs):
|
||||
if not hasattr(cls, '_leaf_classes'):
|
||||
cls._leaf_classes = set()
|
||||
leaf_classes = getattr(cls, '_leaf_classes')
|
||||
leaf_classes.add(cls)
|
||||
# remove any base classes
|
||||
leaf_classes -= set(bases)
|
||||
|
||||
|
||||
class TagRegistered(type):
|
||||
"""
|
||||
As classes of this metaclass are created, they keep a registry in the
|
||||
base class of all classes by a class attribute, indicated by attr_name.
|
||||
"""
|
||||
attr_name = 'tag'
|
||||
|
||||
def __init__(cls, name, bases, namespace):
|
||||
super(TagRegistered, cls).__init__(name, bases, namespace)
|
||||
if not hasattr(cls, '_registry'):
|
||||
cls._registry = {}
|
||||
meta = cls.__class__
|
||||
attr = getattr(cls, meta.attr_name, None)
|
||||
if attr:
|
||||
cls._registry[attr] = cls
|
67
libs/win/jaraco/classes/properties.py
Normal file
67
libs/win/jaraco/classes/properties.py
Normal file
|
@ -0,0 +1,67 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
import six
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
class NonDataProperty:
|
||||
"""Much like the property builtin, but only implements __get__,
|
||||
making it a non-data property, and can be subsequently reset.
|
||||
|
||||
See http://users.rcn.com/python/download/Descriptor.htm for more
|
||||
information.
|
||||
|
||||
>>> class X(object):
|
||||
... @NonDataProperty
|
||||
... def foo(self):
|
||||
... return 3
|
||||
>>> x = X()
|
||||
>>> x.foo
|
||||
3
|
||||
>>> x.foo = 4
|
||||
>>> x.foo
|
||||
4
|
||||
"""
|
||||
|
||||
def __init__(self, fget):
|
||||
assert fget is not None, "fget cannot be none"
|
||||
assert six.callable(fget), "fget must be callable"
|
||||
self.fget = fget
|
||||
|
||||
def __get__(self, obj, objtype=None):
|
||||
if obj is None:
|
||||
return self
|
||||
return self.fget(obj)
|
||||
|
||||
|
||||
# from http://stackoverflow.com/a/5191224
|
||||
class ClassPropertyDescriptor:
|
||||
|
||||
def __init__(self, fget, fset=None):
|
||||
self.fget = fget
|
||||
self.fset = fset
|
||||
|
||||
def __get__(self, obj, klass=None):
|
||||
if klass is None:
|
||||
klass = type(obj)
|
||||
return self.fget.__get__(obj, klass)()
|
||||
|
||||
def __set__(self, obj, value):
|
||||
if not self.fset:
|
||||
raise AttributeError("can't set attribute")
|
||||
type_ = type(obj)
|
||||
return self.fset.__get__(obj, type_)(value)
|
||||
|
||||
def setter(self, func):
|
||||
if not isinstance(func, (classmethod, staticmethod)):
|
||||
func = classmethod(func)
|
||||
self.fset = func
|
||||
return self
|
||||
|
||||
|
||||
def classproperty(func):
|
||||
if not isinstance(func, (classmethod, staticmethod)):
|
||||
func = classmethod(func)
|
||||
|
||||
return ClassPropertyDescriptor(func)
|
906
libs/win/jaraco/collections.py
Normal file
906
libs/win/jaraco/collections.py
Normal file
|
@ -0,0 +1,906 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import absolute_import, unicode_literals, division
|
||||
|
||||
import re
|
||||
import operator
|
||||
import collections
|
||||
import itertools
|
||||
import copy
|
||||
import functools
|
||||
|
||||
try:
|
||||
import collections.abc
|
||||
except ImportError:
|
||||
# Python 2.7
|
||||
collections.abc = collections
|
||||
|
||||
import six
|
||||
from jaraco.classes.properties import NonDataProperty
|
||||
import jaraco.text
|
||||
|
||||
|
||||
class Projection(collections.abc.Mapping):
|
||||
"""
|
||||
Project a set of keys over a mapping
|
||||
|
||||
>>> sample = {'a': 1, 'b': 2, 'c': 3}
|
||||
>>> prj = Projection(['a', 'c', 'd'], sample)
|
||||
>>> prj == {'a': 1, 'c': 3}
|
||||
True
|
||||
|
||||
Keys should only appear if they were specified and exist in the space.
|
||||
|
||||
>>> sorted(list(prj.keys()))
|
||||
['a', 'c']
|
||||
|
||||
Use the projection to update another dict.
|
||||
|
||||
>>> target = {'a': 2, 'b': 2}
|
||||
>>> target.update(prj)
|
||||
>>> target == {'a': 1, 'b': 2, 'c': 3}
|
||||
True
|
||||
|
||||
Also note that Projection keeps a reference to the original dict, so
|
||||
if you modify the original dict, that could modify the Projection.
|
||||
|
||||
>>> del sample['a']
|
||||
>>> dict(prj)
|
||||
{'c': 3}
|
||||
"""
|
||||
def __init__(self, keys, space):
|
||||
self._keys = tuple(keys)
|
||||
self._space = space
|
||||
|
||||
def __getitem__(self, key):
|
||||
if key not in self._keys:
|
||||
raise KeyError(key)
|
||||
return self._space[key]
|
||||
|
||||
def __iter__(self):
|
||||
return iter(set(self._keys).intersection(self._space))
|
||||
|
||||
def __len__(self):
|
||||
return len(tuple(iter(self)))
|
||||
|
||||
|
||||
class DictFilter(object):
|
||||
"""
|
||||
Takes a dict, and simulates a sub-dict based on the keys.
|
||||
|
||||
>>> sample = {'a': 1, 'b': 2, 'c': 3}
|
||||
>>> filtered = DictFilter(sample, ['a', 'c'])
|
||||
>>> filtered == {'a': 1, 'c': 3}
|
||||
True
|
||||
|
||||
One can also filter by a regular expression pattern
|
||||
|
||||
>>> sample['d'] = 4
|
||||
>>> sample['ef'] = 5
|
||||
|
||||
Here we filter for only single-character keys
|
||||
|
||||
>>> filtered = DictFilter(sample, include_pattern='.$')
|
||||
>>> filtered == {'a': 1, 'b': 2, 'c': 3, 'd': 4}
|
||||
True
|
||||
|
||||
Also note that DictFilter keeps a reference to the original dict, so
|
||||
if you modify the original dict, that could modify the filtered dict.
|
||||
|
||||
>>> del sample['d']
|
||||
>>> del sample['a']
|
||||
>>> filtered == {'b': 2, 'c': 3}
|
||||
True
|
||||
|
||||
"""
|
||||
def __init__(self, dict, include_keys=[], include_pattern=None):
|
||||
self.dict = dict
|
||||
self.specified_keys = set(include_keys)
|
||||
if include_pattern is not None:
|
||||
self.include_pattern = re.compile(include_pattern)
|
||||
else:
|
||||
# for performance, replace the pattern_keys property
|
||||
self.pattern_keys = set()
|
||||
|
||||
def get_pattern_keys(self):
|
||||
keys = filter(self.include_pattern.match, self.dict.keys())
|
||||
return set(keys)
|
||||
pattern_keys = NonDataProperty(get_pattern_keys)
|
||||
|
||||
@property
|
||||
def include_keys(self):
|
||||
return self.specified_keys.union(self.pattern_keys)
|
||||
|
||||
def keys(self):
|
||||
return self.include_keys.intersection(self.dict.keys())
|
||||
|
||||
def values(self):
|
||||
keys = self.keys()
|
||||
values = map(self.dict.get, keys)
|
||||
return values
|
||||
|
||||
def __getitem__(self, i):
|
||||
if i not in self.include_keys:
|
||||
return KeyError, i
|
||||
return self.dict[i]
|
||||
|
||||
def items(self):
|
||||
keys = self.keys()
|
||||
values = map(self.dict.get, keys)
|
||||
return zip(keys, values)
|
||||
|
||||
def __eq__(self, other):
|
||||
return dict(self) == other
|
||||
|
||||
def __ne__(self, other):
|
||||
return dict(self) != other
|
||||
|
||||
|
||||
def dict_map(function, dictionary):
|
||||
"""
|
||||
dict_map is much like the built-in function map. It takes a dictionary
|
||||
and applys a function to the values of that dictionary, returning a
|
||||
new dictionary with the mapped values in the original keys.
|
||||
|
||||
>>> d = dict_map(lambda x:x+1, dict(a=1, b=2))
|
||||
>>> d == dict(a=2,b=3)
|
||||
True
|
||||
"""
|
||||
return dict((key, function(value)) for key, value in dictionary.items())
|
||||
|
||||
|
||||
class RangeMap(dict):
|
||||
"""
|
||||
A dictionary-like object that uses the keys as bounds for a range.
|
||||
Inclusion of the value for that range is determined by the
|
||||
key_match_comparator, which defaults to less-than-or-equal.
|
||||
A value is returned for a key if it is the first key that matches in
|
||||
the sorted list of keys.
|
||||
|
||||
One may supply keyword parameters to be passed to the sort function used
|
||||
to sort keys (i.e. cmp [python 2 only], keys, reverse) as sort_params.
|
||||
|
||||
Let's create a map that maps 1-3 -> 'a', 4-6 -> 'b'
|
||||
|
||||
>>> r = RangeMap({3: 'a', 6: 'b'}) # boy, that was easy
|
||||
>>> r[1], r[2], r[3], r[4], r[5], r[6]
|
||||
('a', 'a', 'a', 'b', 'b', 'b')
|
||||
|
||||
Even float values should work so long as the comparison operator
|
||||
supports it.
|
||||
|
||||
>>> r[4.5]
|
||||
'b'
|
||||
|
||||
But you'll notice that the way rangemap is defined, it must be open-ended
|
||||
on one side.
|
||||
|
||||
>>> r[0]
|
||||
'a'
|
||||
>>> r[-1]
|
||||
'a'
|
||||
|
||||
One can close the open-end of the RangeMap by using undefined_value
|
||||
|
||||
>>> r = RangeMap({0: RangeMap.undefined_value, 3: 'a', 6: 'b'})
|
||||
>>> r[0]
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
KeyError: 0
|
||||
|
||||
One can get the first or last elements in the range by using RangeMap.Item
|
||||
|
||||
>>> last_item = RangeMap.Item(-1)
|
||||
>>> r[last_item]
|
||||
'b'
|
||||
|
||||
.last_item is a shortcut for Item(-1)
|
||||
|
||||
>>> r[RangeMap.last_item]
|
||||
'b'
|
||||
|
||||
Sometimes it's useful to find the bounds for a RangeMap
|
||||
|
||||
>>> r.bounds()
|
||||
(0, 6)
|
||||
|
||||
RangeMap supports .get(key, default)
|
||||
|
||||
>>> r.get(0, 'not found')
|
||||
'not found'
|
||||
|
||||
>>> r.get(7, 'not found')
|
||||
'not found'
|
||||
"""
|
||||
def __init__(self, source, sort_params={}, key_match_comparator=operator.le):
|
||||
dict.__init__(self, source)
|
||||
self.sort_params = sort_params
|
||||
self.match = key_match_comparator
|
||||
|
||||
def __getitem__(self, item):
|
||||
sorted_keys = sorted(self.keys(), **self.sort_params)
|
||||
if isinstance(item, RangeMap.Item):
|
||||
result = self.__getitem__(sorted_keys[item])
|
||||
else:
|
||||
key = self._find_first_match_(sorted_keys, item)
|
||||
result = dict.__getitem__(self, key)
|
||||
if result is RangeMap.undefined_value:
|
||||
raise KeyError(key)
|
||||
return result
|
||||
|
||||
def get(self, key, default=None):
|
||||
"""
|
||||
Return the value for key if key is in the dictionary, else default.
|
||||
If default is not given, it defaults to None, so that this method
|
||||
never raises a KeyError.
|
||||
"""
|
||||
try:
|
||||
return self[key]
|
||||
except KeyError:
|
||||
return default
|
||||
|
||||
def _find_first_match_(self, keys, item):
|
||||
is_match = functools.partial(self.match, item)
|
||||
matches = list(filter(is_match, keys))
|
||||
if matches:
|
||||
return matches[0]
|
||||
raise KeyError(item)
|
||||
|
||||
def bounds(self):
|
||||
sorted_keys = sorted(self.keys(), **self.sort_params)
|
||||
return (
|
||||
sorted_keys[RangeMap.first_item],
|
||||
sorted_keys[RangeMap.last_item],
|
||||
)
|
||||
|
||||
# some special values for the RangeMap
|
||||
undefined_value = type(str('RangeValueUndefined'), (object,), {})()
|
||||
|
||||
class Item(int):
|
||||
"RangeMap Item"
|
||||
first_item = Item(0)
|
||||
last_item = Item(-1)
|
||||
|
||||
|
||||
def __identity(x):
|
||||
return x
|
||||
|
||||
|
||||
def sorted_items(d, key=__identity, reverse=False):
|
||||
"""
|
||||
Return the items of the dictionary sorted by the keys
|
||||
|
||||
>>> sample = dict(foo=20, bar=42, baz=10)
|
||||
>>> tuple(sorted_items(sample))
|
||||
(('bar', 42), ('baz', 10), ('foo', 20))
|
||||
|
||||
>>> reverse_string = lambda s: ''.join(reversed(s))
|
||||
>>> tuple(sorted_items(sample, key=reverse_string))
|
||||
(('foo', 20), ('bar', 42), ('baz', 10))
|
||||
|
||||
>>> tuple(sorted_items(sample, reverse=True))
|
||||
(('foo', 20), ('baz', 10), ('bar', 42))
|
||||
"""
|
||||
# wrap the key func so it operates on the first element of each item
|
||||
def pairkey_key(item):
|
||||
return key(item[0])
|
||||
return sorted(d.items(), key=pairkey_key, reverse=reverse)
|
||||
|
||||
|
||||
class KeyTransformingDict(dict):
|
||||
"""
|
||||
A dict subclass that transforms the keys before they're used.
|
||||
Subclasses may override the default transform_key to customize behavior.
|
||||
"""
|
||||
@staticmethod
|
||||
def transform_key(key):
|
||||
return key
|
||||
|
||||
def __init__(self, *args, **kargs):
|
||||
super(KeyTransformingDict, self).__init__()
|
||||
# build a dictionary using the default constructs
|
||||
d = dict(*args, **kargs)
|
||||
# build this dictionary using transformed keys.
|
||||
for item in d.items():
|
||||
self.__setitem__(*item)
|
||||
|
||||
def __setitem__(self, key, val):
|
||||
key = self.transform_key(key)
|
||||
super(KeyTransformingDict, self).__setitem__(key, val)
|
||||
|
||||
def __getitem__(self, key):
|
||||
key = self.transform_key(key)
|
||||
return super(KeyTransformingDict, self).__getitem__(key)
|
||||
|
||||
def __contains__(self, key):
|
||||
key = self.transform_key(key)
|
||||
return super(KeyTransformingDict, self).__contains__(key)
|
||||
|
||||
def __delitem__(self, key):
|
||||
key = self.transform_key(key)
|
||||
return super(KeyTransformingDict, self).__delitem__(key)
|
||||
|
||||
def get(self, key, *args, **kwargs):
|
||||
key = self.transform_key(key)
|
||||
return super(KeyTransformingDict, self).get(key, *args, **kwargs)
|
||||
|
||||
def setdefault(self, key, *args, **kwargs):
|
||||
key = self.transform_key(key)
|
||||
return super(KeyTransformingDict, self).setdefault(key, *args, **kwargs)
|
||||
|
||||
def pop(self, key, *args, **kwargs):
|
||||
key = self.transform_key(key)
|
||||
return super(KeyTransformingDict, self).pop(key, *args, **kwargs)
|
||||
|
||||
def matching_key_for(self, key):
|
||||
"""
|
||||
Given a key, return the actual key stored in self that matches.
|
||||
Raise KeyError if the key isn't found.
|
||||
"""
|
||||
try:
|
||||
return next(e_key for e_key in self.keys() if e_key == key)
|
||||
except StopIteration:
|
||||
raise KeyError(key)
|
||||
|
||||
|
||||
class FoldedCaseKeyedDict(KeyTransformingDict):
|
||||
"""
|
||||
A case-insensitive dictionary (keys are compared as insensitive
|
||||
if they are strings).
|
||||
|
||||
>>> d = FoldedCaseKeyedDict()
|
||||
>>> d['heLlo'] = 'world'
|
||||
>>> list(d.keys()) == ['heLlo']
|
||||
True
|
||||
>>> list(d.values()) == ['world']
|
||||
True
|
||||
>>> d['hello'] == 'world'
|
||||
True
|
||||
>>> 'hello' in d
|
||||
True
|
||||
>>> 'HELLO' in d
|
||||
True
|
||||
>>> print(repr(FoldedCaseKeyedDict({'heLlo': 'world'})).replace("u'", "'"))
|
||||
{'heLlo': 'world'}
|
||||
>>> d = FoldedCaseKeyedDict({'heLlo': 'world'})
|
||||
>>> print(d['hello'])
|
||||
world
|
||||
>>> print(d['Hello'])
|
||||
world
|
||||
>>> list(d.keys())
|
||||
['heLlo']
|
||||
>>> d = FoldedCaseKeyedDict({'heLlo': 'world', 'Hello': 'world'})
|
||||
>>> list(d.values())
|
||||
['world']
|
||||
>>> key, = d.keys()
|
||||
>>> key in ['heLlo', 'Hello']
|
||||
True
|
||||
>>> del d['HELLO']
|
||||
>>> d
|
||||
{}
|
||||
|
||||
get should work
|
||||
|
||||
>>> d['Sumthin'] = 'else'
|
||||
>>> d.get('SUMTHIN')
|
||||
'else'
|
||||
>>> d.get('OTHER', 'thing')
|
||||
'thing'
|
||||
>>> del d['sumthin']
|
||||
|
||||
setdefault should also work
|
||||
|
||||
>>> d['This'] = 'that'
|
||||
>>> print(d.setdefault('this', 'other'))
|
||||
that
|
||||
>>> len(d)
|
||||
1
|
||||
>>> print(d['this'])
|
||||
that
|
||||
>>> print(d.setdefault('That', 'other'))
|
||||
other
|
||||
>>> print(d['THAT'])
|
||||
other
|
||||
|
||||
Make it pop!
|
||||
|
||||
>>> print(d.pop('THAT'))
|
||||
other
|
||||
|
||||
To retrieve the key in its originally-supplied form, use matching_key_for
|
||||
|
||||
>>> print(d.matching_key_for('this'))
|
||||
This
|
||||
"""
|
||||
@staticmethod
|
||||
def transform_key(key):
|
||||
return jaraco.text.FoldedCase(key)
|
||||
|
||||
|
||||
class DictAdapter(object):
|
||||
"""
|
||||
Provide a getitem interface for attributes of an object.
|
||||
|
||||
Let's say you want to get at the string.lowercase property in a formatted
|
||||
string. It's easy with DictAdapter.
|
||||
|
||||
>>> import string
|
||||
>>> print("lowercase is %(ascii_lowercase)s" % DictAdapter(string))
|
||||
lowercase is abcdefghijklmnopqrstuvwxyz
|
||||
"""
|
||||
def __init__(self, wrapped_ob):
|
||||
self.object = wrapped_ob
|
||||
|
||||
def __getitem__(self, name):
|
||||
return getattr(self.object, name)
|
||||
|
||||
|
||||
class ItemsAsAttributes(object):
|
||||
"""
|
||||
Mix-in class to enable a mapping object to provide items as
|
||||
attributes.
|
||||
|
||||
>>> C = type(str('C'), (dict, ItemsAsAttributes), dict())
|
||||
>>> i = C()
|
||||
>>> i['foo'] = 'bar'
|
||||
>>> i.foo
|
||||
'bar'
|
||||
|
||||
Natural attribute access takes precedence
|
||||
|
||||
>>> i.foo = 'henry'
|
||||
>>> i.foo
|
||||
'henry'
|
||||
|
||||
But as you might expect, the mapping functionality is preserved.
|
||||
|
||||
>>> i['foo']
|
||||
'bar'
|
||||
|
||||
A normal attribute error should be raised if an attribute is
|
||||
requested that doesn't exist.
|
||||
|
||||
>>> i.missing
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
AttributeError: 'C' object has no attribute 'missing'
|
||||
|
||||
It also works on dicts that customize __getitem__
|
||||
|
||||
>>> missing_func = lambda self, key: 'missing item'
|
||||
>>> C = type(
|
||||
... str('C'),
|
||||
... (dict, ItemsAsAttributes),
|
||||
... dict(__missing__ = missing_func),
|
||||
... )
|
||||
>>> i = C()
|
||||
>>> i.missing
|
||||
'missing item'
|
||||
>>> i.foo
|
||||
'missing item'
|
||||
"""
|
||||
def __getattr__(self, key):
|
||||
try:
|
||||
return getattr(super(ItemsAsAttributes, self), key)
|
||||
except AttributeError as e:
|
||||
# attempt to get the value from the mapping (return self[key])
|
||||
# but be careful not to lose the original exception context.
|
||||
noval = object()
|
||||
|
||||
def _safe_getitem(cont, key, missing_result):
|
||||
try:
|
||||
return cont[key]
|
||||
except KeyError:
|
||||
return missing_result
|
||||
result = _safe_getitem(self, key, noval)
|
||||
if result is not noval:
|
||||
return result
|
||||
# raise the original exception, but use the original class
|
||||
# name, not 'super'.
|
||||
message, = e.args
|
||||
message = message.replace('super', self.__class__.__name__, 1)
|
||||
e.args = message,
|
||||
raise
|
||||
|
||||
|
||||
def invert_map(map):
|
||||
"""
|
||||
Given a dictionary, return another dictionary with keys and values
|
||||
switched. If any of the values resolve to the same key, raises
|
||||
a ValueError.
|
||||
|
||||
>>> numbers = dict(a=1, b=2, c=3)
|
||||
>>> letters = invert_map(numbers)
|
||||
>>> letters[1]
|
||||
'a'
|
||||
>>> numbers['d'] = 3
|
||||
>>> invert_map(numbers)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Key conflict in inverted mapping
|
||||
"""
|
||||
res = dict((v, k) for k, v in map.items())
|
||||
if not len(res) == len(map):
|
||||
raise ValueError('Key conflict in inverted mapping')
|
||||
return res
|
||||
|
||||
|
||||
class IdentityOverrideMap(dict):
|
||||
"""
|
||||
A dictionary that by default maps each key to itself, but otherwise
|
||||
acts like a normal dictionary.
|
||||
|
||||
>>> d = IdentityOverrideMap()
|
||||
>>> d[42]
|
||||
42
|
||||
>>> d['speed'] = 'speedo'
|
||||
>>> print(d['speed'])
|
||||
speedo
|
||||
"""
|
||||
|
||||
def __missing__(self, key):
|
||||
return key
|
||||
|
||||
|
||||
class DictStack(list, collections.abc.Mapping):
|
||||
"""
|
||||
A stack of dictionaries that behaves as a view on those dictionaries,
|
||||
giving preference to the last.
|
||||
|
||||
>>> stack = DictStack([dict(a=1, c=2), dict(b=2, a=2)])
|
||||
>>> stack['a']
|
||||
2
|
||||
>>> stack['b']
|
||||
2
|
||||
>>> stack['c']
|
||||
2
|
||||
>>> stack.push(dict(a=3))
|
||||
>>> stack['a']
|
||||
3
|
||||
>>> set(stack.keys()) == set(['a', 'b', 'c'])
|
||||
True
|
||||
>>> d = stack.pop()
|
||||
>>> stack['a']
|
||||
2
|
||||
>>> d = stack.pop()
|
||||
>>> stack['a']
|
||||
1
|
||||
>>> stack.get('b', None)
|
||||
"""
|
||||
|
||||
def keys(self):
|
||||
return list(set(itertools.chain.from_iterable(c.keys() for c in self)))
|
||||
|
||||
def __getitem__(self, key):
|
||||
for scope in reversed(self):
|
||||
if key in scope:
|
||||
return scope[key]
|
||||
raise KeyError(key)
|
||||
|
||||
push = list.append
|
||||
|
||||
|
||||
class BijectiveMap(dict):
|
||||
"""
|
||||
A Bijective Map (two-way mapping).
|
||||
|
||||
Implemented as a simple dictionary of 2x the size, mapping values back
|
||||
to keys.
|
||||
|
||||
Note, this implementation may be incomplete. If there's not a test for
|
||||
your use case below, it's likely to fail, so please test and send pull
|
||||
requests or patches for additional functionality needed.
|
||||
|
||||
|
||||
>>> m = BijectiveMap()
|
||||
>>> m['a'] = 'b'
|
||||
>>> m == {'a': 'b', 'b': 'a'}
|
||||
True
|
||||
>>> print(m['b'])
|
||||
a
|
||||
|
||||
>>> m['c'] = 'd'
|
||||
>>> len(m)
|
||||
2
|
||||
|
||||
Some weird things happen if you map an item to itself or overwrite a
|
||||
single key of a pair, so it's disallowed.
|
||||
|
||||
>>> m['e'] = 'e'
|
||||
Traceback (most recent call last):
|
||||
ValueError: Key cannot map to itself
|
||||
|
||||
>>> m['d'] = 'e'
|
||||
Traceback (most recent call last):
|
||||
ValueError: Key/Value pairs may not overlap
|
||||
|
||||
>>> m['e'] = 'd'
|
||||
Traceback (most recent call last):
|
||||
ValueError: Key/Value pairs may not overlap
|
||||
|
||||
>>> print(m.pop('d'))
|
||||
c
|
||||
|
||||
>>> 'c' in m
|
||||
False
|
||||
|
||||
>>> m = BijectiveMap(dict(a='b'))
|
||||
>>> len(m)
|
||||
1
|
||||
>>> print(m['b'])
|
||||
a
|
||||
|
||||
>>> m = BijectiveMap()
|
||||
>>> m.update(a='b')
|
||||
>>> m['b']
|
||||
'a'
|
||||
|
||||
>>> del m['b']
|
||||
>>> len(m)
|
||||
0
|
||||
>>> 'a' in m
|
||||
False
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(BijectiveMap, self).__init__()
|
||||
self.update(*args, **kwargs)
|
||||
|
||||
def __setitem__(self, item, value):
|
||||
if item == value:
|
||||
raise ValueError("Key cannot map to itself")
|
||||
overlap = (
|
||||
item in self and self[item] != value
|
||||
or
|
||||
value in self and self[value] != item
|
||||
)
|
||||
if overlap:
|
||||
raise ValueError("Key/Value pairs may not overlap")
|
||||
super(BijectiveMap, self).__setitem__(item, value)
|
||||
super(BijectiveMap, self).__setitem__(value, item)
|
||||
|
||||
def __delitem__(self, item):
|
||||
self.pop(item)
|
||||
|
||||
def __len__(self):
|
||||
return super(BijectiveMap, self).__len__() // 2
|
||||
|
||||
def pop(self, key, *args, **kwargs):
|
||||
mirror = self[key]
|
||||
super(BijectiveMap, self).__delitem__(mirror)
|
||||
return super(BijectiveMap, self).pop(key, *args, **kwargs)
|
||||
|
||||
def update(self, *args, **kwargs):
|
||||
# build a dictionary using the default constructs
|
||||
d = dict(*args, **kwargs)
|
||||
# build this dictionary using transformed keys.
|
||||
for item in d.items():
|
||||
self.__setitem__(*item)
|
||||
|
||||
|
||||
class FrozenDict(collections.abc.Mapping, collections.abc.Hashable):
|
||||
"""
|
||||
An immutable mapping.
|
||||
|
||||
>>> a = FrozenDict(a=1, b=2)
|
||||
>>> b = FrozenDict(a=1, b=2)
|
||||
>>> a == b
|
||||
True
|
||||
|
||||
>>> a == dict(a=1, b=2)
|
||||
True
|
||||
>>> dict(a=1, b=2) == a
|
||||
True
|
||||
|
||||
>>> a['c'] = 3
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: 'FrozenDict' object does not support item assignment
|
||||
|
||||
>>> a.update(y=3)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
AttributeError: 'FrozenDict' object has no attribute 'update'
|
||||
|
||||
Copies should compare equal
|
||||
|
||||
>>> copy.copy(a) == a
|
||||
True
|
||||
|
||||
Copies should be the same type
|
||||
|
||||
>>> isinstance(copy.copy(a), FrozenDict)
|
||||
True
|
||||
|
||||
FrozenDict supplies .copy(), even though
|
||||
collections.abc.Mapping doesn't demand it.
|
||||
|
||||
>>> a.copy() == a
|
||||
True
|
||||
>>> a.copy() is not a
|
||||
True
|
||||
"""
|
||||
__slots__ = ['__data']
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
self = super(FrozenDict, cls).__new__(cls)
|
||||
self.__data = dict(*args, **kwargs)
|
||||
return self
|
||||
|
||||
# Container
|
||||
def __contains__(self, key):
|
||||
return key in self.__data
|
||||
|
||||
# Hashable
|
||||
def __hash__(self):
|
||||
return hash(tuple(sorted(self.__data.iteritems())))
|
||||
|
||||
# Mapping
|
||||
def __iter__(self):
|
||||
return iter(self.__data)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.__data)
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self.__data[key]
|
||||
|
||||
# override get for efficiency provided by dict
|
||||
def get(self, *args, **kwargs):
|
||||
return self.__data.get(*args, **kwargs)
|
||||
|
||||
# override eq to recognize underlying implementation
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, FrozenDict):
|
||||
other = other.__data
|
||||
return self.__data.__eq__(other)
|
||||
|
||||
def copy(self):
|
||||
"Return a shallow copy of self"
|
||||
return copy.copy(self)
|
||||
|
||||
|
||||
class Enumeration(ItemsAsAttributes, BijectiveMap):
|
||||
"""
|
||||
A convenient way to provide enumerated values
|
||||
|
||||
>>> e = Enumeration('a b c')
|
||||
>>> e['a']
|
||||
0
|
||||
|
||||
>>> e.a
|
||||
0
|
||||
|
||||
>>> e[1]
|
||||
'b'
|
||||
|
||||
>>> set(e.names) == set('abc')
|
||||
True
|
||||
|
||||
>>> set(e.codes) == set(range(3))
|
||||
True
|
||||
|
||||
>>> e.get('d') is None
|
||||
True
|
||||
|
||||
Codes need not start with 0
|
||||
|
||||
>>> e = Enumeration('a b c', range(1, 4))
|
||||
>>> e['a']
|
||||
1
|
||||
|
||||
>>> e[3]
|
||||
'c'
|
||||
"""
|
||||
def __init__(self, names, codes=None):
|
||||
if isinstance(names, six.string_types):
|
||||
names = names.split()
|
||||
if codes is None:
|
||||
codes = itertools.count()
|
||||
super(Enumeration, self).__init__(zip(names, codes))
|
||||
|
||||
@property
|
||||
def names(self):
|
||||
return (key for key in self if isinstance(key, six.string_types))
|
||||
|
||||
@property
|
||||
def codes(self):
|
||||
return (self[name] for name in self.names)
|
||||
|
||||
|
||||
class Everything(object):
|
||||
"""
|
||||
A collection "containing" every possible thing.
|
||||
|
||||
>>> 'foo' in Everything()
|
||||
True
|
||||
|
||||
>>> import random
|
||||
>>> random.randint(1, 999) in Everything()
|
||||
True
|
||||
|
||||
>>> random.choice([None, 'foo', 42, ('a', 'b', 'c')]) in Everything()
|
||||
True
|
||||
"""
|
||||
def __contains__(self, other):
|
||||
return True
|
||||
|
||||
|
||||
class InstrumentedDict(six.moves.UserDict):
|
||||
"""
|
||||
Instrument an existing dictionary with additional
|
||||
functionality, but always reference and mutate
|
||||
the original dictionary.
|
||||
|
||||
>>> orig = {'a': 1, 'b': 2}
|
||||
>>> inst = InstrumentedDict(orig)
|
||||
>>> inst['a']
|
||||
1
|
||||
>>> inst['c'] = 3
|
||||
>>> orig['c']
|
||||
3
|
||||
>>> inst.keys() == orig.keys()
|
||||
True
|
||||
"""
|
||||
def __init__(self, data):
|
||||
six.moves.UserDict.__init__(self)
|
||||
self.data = data
|
||||
|
||||
|
||||
class Least(object):
|
||||
"""
|
||||
A value that is always lesser than any other
|
||||
|
||||
>>> least = Least()
|
||||
>>> 3 < least
|
||||
False
|
||||
>>> 3 > least
|
||||
True
|
||||
>>> least < 3
|
||||
True
|
||||
>>> least <= 3
|
||||
True
|
||||
>>> least > 3
|
||||
False
|
||||
>>> 'x' > least
|
||||
True
|
||||
>>> None > least
|
||||
True
|
||||
"""
|
||||
|
||||
def __le__(self, other):
|
||||
return True
|
||||
__lt__ = __le__
|
||||
|
||||
def __ge__(self, other):
|
||||
return False
|
||||
__gt__ = __ge__
|
||||
|
||||
|
||||
class Greatest(object):
|
||||
"""
|
||||
A value that is always greater than any other
|
||||
|
||||
>>> greatest = Greatest()
|
||||
>>> 3 < greatest
|
||||
True
|
||||
>>> 3 > greatest
|
||||
False
|
||||
>>> greatest < 3
|
||||
False
|
||||
>>> greatest > 3
|
||||
True
|
||||
>>> greatest >= 3
|
||||
True
|
||||
>>> 'x' > greatest
|
||||
False
|
||||
>>> None > greatest
|
||||
False
|
||||
"""
|
||||
|
||||
def __ge__(self, other):
|
||||
return True
|
||||
__gt__ = __ge__
|
||||
|
||||
def __le__(self, other):
|
||||
return False
|
||||
__lt__ = __le__
|
459
libs/win/jaraco/functools.py
Normal file
459
libs/win/jaraco/functools.py
Normal file
|
@ -0,0 +1,459 @@
|
|||
from __future__ import (
|
||||
absolute_import, unicode_literals, print_function, division,
|
||||
)
|
||||
|
||||
import functools
|
||||
import time
|
||||
import warnings
|
||||
import inspect
|
||||
import collections
|
||||
from itertools import count
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
try:
|
||||
from functools import lru_cache
|
||||
except ImportError:
|
||||
try:
|
||||
from backports.functools_lru_cache import lru_cache
|
||||
except ImportError:
|
||||
try:
|
||||
from functools32 import lru_cache
|
||||
except ImportError:
|
||||
warnings.warn("No lru_cache available")
|
||||
|
||||
|
||||
import more_itertools.recipes
|
||||
|
||||
|
||||
def compose(*funcs):
|
||||
"""
|
||||
Compose any number of unary functions into a single unary function.
|
||||
|
||||
>>> import textwrap
|
||||
>>> from six import text_type
|
||||
>>> stripped = text_type.strip(textwrap.dedent(compose.__doc__))
|
||||
>>> compose(text_type.strip, textwrap.dedent)(compose.__doc__) == stripped
|
||||
True
|
||||
|
||||
Compose also allows the innermost function to take arbitrary arguments.
|
||||
|
||||
>>> round_three = lambda x: round(x, ndigits=3)
|
||||
>>> f = compose(round_three, int.__truediv__)
|
||||
>>> [f(3*x, x+1) for x in range(1,10)]
|
||||
[1.5, 2.0, 2.25, 2.4, 2.5, 2.571, 2.625, 2.667, 2.7]
|
||||
"""
|
||||
|
||||
def compose_two(f1, f2):
|
||||
return lambda *args, **kwargs: f1(f2(*args, **kwargs))
|
||||
return functools.reduce(compose_two, funcs)
|
||||
|
||||
|
||||
def method_caller(method_name, *args, **kwargs):
|
||||
"""
|
||||
Return a function that will call a named method on the
|
||||
target object with optional positional and keyword
|
||||
arguments.
|
||||
|
||||
>>> lower = method_caller('lower')
|
||||
>>> lower('MyString')
|
||||
'mystring'
|
||||
"""
|
||||
def call_method(target):
|
||||
func = getattr(target, method_name)
|
||||
return func(*args, **kwargs)
|
||||
return call_method
|
||||
|
||||
|
||||
def once(func):
|
||||
"""
|
||||
Decorate func so it's only ever called the first time.
|
||||
|
||||
This decorator can ensure that an expensive or non-idempotent function
|
||||
will not be expensive on subsequent calls and is idempotent.
|
||||
|
||||
>>> add_three = once(lambda a: a+3)
|
||||
>>> add_three(3)
|
||||
6
|
||||
>>> add_three(9)
|
||||
6
|
||||
>>> add_three('12')
|
||||
6
|
||||
|
||||
To reset the stored value, simply clear the property ``saved_result``.
|
||||
|
||||
>>> del add_three.saved_result
|
||||
>>> add_three(9)
|
||||
12
|
||||
>>> add_three(8)
|
||||
12
|
||||
|
||||
Or invoke 'reset()' on it.
|
||||
|
||||
>>> add_three.reset()
|
||||
>>> add_three(-3)
|
||||
0
|
||||
>>> add_three(0)
|
||||
0
|
||||
"""
|
||||
@functools.wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
if not hasattr(wrapper, 'saved_result'):
|
||||
wrapper.saved_result = func(*args, **kwargs)
|
||||
return wrapper.saved_result
|
||||
wrapper.reset = lambda: vars(wrapper).__delitem__('saved_result')
|
||||
return wrapper
|
||||
|
||||
|
||||
def method_cache(method, cache_wrapper=None):
|
||||
"""
|
||||
Wrap lru_cache to support storing the cache data in the object instances.
|
||||
|
||||
Abstracts the common paradigm where the method explicitly saves an
|
||||
underscore-prefixed protected property on first call and returns that
|
||||
subsequently.
|
||||
|
||||
>>> class MyClass:
|
||||
... calls = 0
|
||||
...
|
||||
... @method_cache
|
||||
... def method(self, value):
|
||||
... self.calls += 1
|
||||
... return value
|
||||
|
||||
>>> a = MyClass()
|
||||
>>> a.method(3)
|
||||
3
|
||||
>>> for x in range(75):
|
||||
... res = a.method(x)
|
||||
>>> a.calls
|
||||
75
|
||||
|
||||
Note that the apparent behavior will be exactly like that of lru_cache
|
||||
except that the cache is stored on each instance, so values in one
|
||||
instance will not flush values from another, and when an instance is
|
||||
deleted, so are the cached values for that instance.
|
||||
|
||||
>>> b = MyClass()
|
||||
>>> for x in range(35):
|
||||
... res = b.method(x)
|
||||
>>> b.calls
|
||||
35
|
||||
>>> a.method(0)
|
||||
0
|
||||
>>> a.calls
|
||||
75
|
||||
|
||||
Note that if method had been decorated with ``functools.lru_cache()``,
|
||||
a.calls would have been 76 (due to the cached value of 0 having been
|
||||
flushed by the 'b' instance).
|
||||
|
||||
Clear the cache with ``.cache_clear()``
|
||||
|
||||
>>> a.method.cache_clear()
|
||||
|
||||
Another cache wrapper may be supplied:
|
||||
|
||||
>>> cache = lru_cache(maxsize=2)
|
||||
>>> MyClass.method2 = method_cache(lambda self: 3, cache_wrapper=cache)
|
||||
>>> a = MyClass()
|
||||
>>> a.method2()
|
||||
3
|
||||
|
||||
Caution - do not subsequently wrap the method with another decorator, such
|
||||
as ``@property``, which changes the semantics of the function.
|
||||
|
||||
See also
|
||||
http://code.activestate.com/recipes/577452-a-memoize-decorator-for-instance-methods/
|
||||
for another implementation and additional justification.
|
||||
"""
|
||||
cache_wrapper = cache_wrapper or lru_cache()
|
||||
|
||||
def wrapper(self, *args, **kwargs):
|
||||
# it's the first call, replace the method with a cached, bound method
|
||||
bound_method = functools.partial(method, self)
|
||||
cached_method = cache_wrapper(bound_method)
|
||||
setattr(self, method.__name__, cached_method)
|
||||
return cached_method(*args, **kwargs)
|
||||
|
||||
return _special_method_cache(method, cache_wrapper) or wrapper
|
||||
|
||||
|
||||
def _special_method_cache(method, cache_wrapper):
|
||||
"""
|
||||
Because Python treats special methods differently, it's not
|
||||
possible to use instance attributes to implement the cached
|
||||
methods.
|
||||
|
||||
Instead, install the wrapper method under a different name
|
||||
and return a simple proxy to that wrapper.
|
||||
|
||||
https://github.com/jaraco/jaraco.functools/issues/5
|
||||
"""
|
||||
name = method.__name__
|
||||
special_names = '__getattr__', '__getitem__'
|
||||
if name not in special_names:
|
||||
return
|
||||
|
||||
wrapper_name = '__cached' + name
|
||||
|
||||
def proxy(self, *args, **kwargs):
|
||||
if wrapper_name not in vars(self):
|
||||
bound = functools.partial(method, self)
|
||||
cache = cache_wrapper(bound)
|
||||
setattr(self, wrapper_name, cache)
|
||||
else:
|
||||
cache = getattr(self, wrapper_name)
|
||||
return cache(*args, **kwargs)
|
||||
|
||||
return proxy
|
||||
|
||||
|
||||
def apply(transform):
|
||||
"""
|
||||
Decorate a function with a transform function that is
|
||||
invoked on results returned from the decorated function.
|
||||
|
||||
>>> @apply(reversed)
|
||||
... def get_numbers(start):
|
||||
... return range(start, start+3)
|
||||
>>> list(get_numbers(4))
|
||||
[6, 5, 4]
|
||||
"""
|
||||
def wrap(func):
|
||||
return compose(transform, func)
|
||||
return wrap
|
||||
|
||||
|
||||
def result_invoke(action):
|
||||
r"""
|
||||
Decorate a function with an action function that is
|
||||
invoked on the results returned from the decorated
|
||||
function (for its side-effect), then return the original
|
||||
result.
|
||||
|
||||
>>> @result_invoke(print)
|
||||
... def add_two(a, b):
|
||||
... return a + b
|
||||
>>> x = add_two(2, 3)
|
||||
5
|
||||
"""
|
||||
def wrap(func):
|
||||
@functools.wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
result = func(*args, **kwargs)
|
||||
action(result)
|
||||
return result
|
||||
return wrapper
|
||||
return wrap
|
||||
|
||||
|
||||
def call_aside(f, *args, **kwargs):
|
||||
"""
|
||||
Call a function for its side effect after initialization.
|
||||
|
||||
>>> @call_aside
|
||||
... def func(): print("called")
|
||||
called
|
||||
>>> func()
|
||||
called
|
||||
|
||||
Use functools.partial to pass parameters to the initial call
|
||||
|
||||
>>> @functools.partial(call_aside, name='bingo')
|
||||
... def func(name): print("called with", name)
|
||||
called with bingo
|
||||
"""
|
||||
f(*args, **kwargs)
|
||||
return f
|
||||
|
||||
|
||||
class Throttler:
|
||||
"""
|
||||
Rate-limit a function (or other callable)
|
||||
"""
|
||||
def __init__(self, func, max_rate=float('Inf')):
|
||||
if isinstance(func, Throttler):
|
||||
func = func.func
|
||||
self.func = func
|
||||
self.max_rate = max_rate
|
||||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
self.last_called = 0
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
self._wait()
|
||||
return self.func(*args, **kwargs)
|
||||
|
||||
def _wait(self):
|
||||
"ensure at least 1/max_rate seconds from last call"
|
||||
elapsed = time.time() - self.last_called
|
||||
must_wait = 1 / self.max_rate - elapsed
|
||||
time.sleep(max(0, must_wait))
|
||||
self.last_called = time.time()
|
||||
|
||||
def __get__(self, obj, type=None):
|
||||
return first_invoke(self._wait, functools.partial(self.func, obj))
|
||||
|
||||
|
||||
def first_invoke(func1, func2):
|
||||
"""
|
||||
Return a function that when invoked will invoke func1 without
|
||||
any parameters (for its side-effect) and then invoke func2
|
||||
with whatever parameters were passed, returning its result.
|
||||
"""
|
||||
def wrapper(*args, **kwargs):
|
||||
func1()
|
||||
return func2(*args, **kwargs)
|
||||
return wrapper
|
||||
|
||||
|
||||
def retry_call(func, cleanup=lambda: None, retries=0, trap=()):
|
||||
"""
|
||||
Given a callable func, trap the indicated exceptions
|
||||
for up to 'retries' times, invoking cleanup on the
|
||||
exception. On the final attempt, allow any exceptions
|
||||
to propagate.
|
||||
"""
|
||||
attempts = count() if retries == float('inf') else range(retries)
|
||||
for attempt in attempts:
|
||||
try:
|
||||
return func()
|
||||
except trap:
|
||||
cleanup()
|
||||
|
||||
return func()
|
||||
|
||||
|
||||
def retry(*r_args, **r_kwargs):
|
||||
"""
|
||||
Decorator wrapper for retry_call. Accepts arguments to retry_call
|
||||
except func and then returns a decorator for the decorated function.
|
||||
|
||||
Ex:
|
||||
|
||||
>>> @retry(retries=3)
|
||||
... def my_func(a, b):
|
||||
... "this is my funk"
|
||||
... print(a, b)
|
||||
>>> my_func.__doc__
|
||||
'this is my funk'
|
||||
"""
|
||||
def decorate(func):
|
||||
@functools.wraps(func)
|
||||
def wrapper(*f_args, **f_kwargs):
|
||||
bound = functools.partial(func, *f_args, **f_kwargs)
|
||||
return retry_call(bound, *r_args, **r_kwargs)
|
||||
return wrapper
|
||||
return decorate
|
||||
|
||||
|
||||
def print_yielded(func):
|
||||
"""
|
||||
Convert a generator into a function that prints all yielded elements
|
||||
|
||||
>>> @print_yielded
|
||||
... def x():
|
||||
... yield 3; yield None
|
||||
>>> x()
|
||||
3
|
||||
None
|
||||
"""
|
||||
print_all = functools.partial(map, print)
|
||||
print_results = compose(more_itertools.recipes.consume, print_all, func)
|
||||
return functools.wraps(func)(print_results)
|
||||
|
||||
|
||||
def pass_none(func):
|
||||
"""
|
||||
Wrap func so it's not called if its first param is None
|
||||
|
||||
>>> print_text = pass_none(print)
|
||||
>>> print_text('text')
|
||||
text
|
||||
>>> print_text(None)
|
||||
"""
|
||||
@functools.wraps(func)
|
||||
def wrapper(param, *args, **kwargs):
|
||||
if param is not None:
|
||||
return func(param, *args, **kwargs)
|
||||
return wrapper
|
||||
|
||||
|
||||
def assign_params(func, namespace):
|
||||
"""
|
||||
Assign parameters from namespace where func solicits.
|
||||
|
||||
>>> def func(x, y=3):
|
||||
... print(x, y)
|
||||
>>> assigned = assign_params(func, dict(x=2, z=4))
|
||||
>>> assigned()
|
||||
2 3
|
||||
|
||||
The usual errors are raised if a function doesn't receive
|
||||
its required parameters:
|
||||
|
||||
>>> assigned = assign_params(func, dict(y=3, z=4))
|
||||
>>> assigned()
|
||||
Traceback (most recent call last):
|
||||
TypeError: func() ...argument...
|
||||
"""
|
||||
try:
|
||||
sig = inspect.signature(func)
|
||||
params = sig.parameters.keys()
|
||||
except AttributeError:
|
||||
spec = inspect.getargspec(func)
|
||||
params = spec.args
|
||||
call_ns = {
|
||||
k: namespace[k]
|
||||
for k in params
|
||||
if k in namespace
|
||||
}
|
||||
return functools.partial(func, **call_ns)
|
||||
|
||||
|
||||
def save_method_args(method):
|
||||
"""
|
||||
Wrap a method such that when it is called, the args and kwargs are
|
||||
saved on the method.
|
||||
|
||||
>>> class MyClass:
|
||||
... @save_method_args
|
||||
... def method(self, a, b):
|
||||
... print(a, b)
|
||||
>>> my_ob = MyClass()
|
||||
>>> my_ob.method(1, 2)
|
||||
1 2
|
||||
>>> my_ob._saved_method.args
|
||||
(1, 2)
|
||||
>>> my_ob._saved_method.kwargs
|
||||
{}
|
||||
>>> my_ob.method(a=3, b='foo')
|
||||
3 foo
|
||||
>>> my_ob._saved_method.args
|
||||
()
|
||||
>>> my_ob._saved_method.kwargs == dict(a=3, b='foo')
|
||||
True
|
||||
|
||||
The arguments are stored on the instance, allowing for
|
||||
different instance to save different args.
|
||||
|
||||
>>> your_ob = MyClass()
|
||||
>>> your_ob.method({str('x'): 3}, b=[4])
|
||||
{'x': 3} [4]
|
||||
>>> your_ob._saved_method.args
|
||||
({'x': 3},)
|
||||
>>> my_ob._saved_method.args
|
||||
()
|
||||
"""
|
||||
args_and_kwargs = collections.namedtuple('args_and_kwargs', 'args kwargs')
|
||||
|
||||
@functools.wraps(method)
|
||||
def wrapper(self, *args, **kwargs):
|
||||
attr_name = '_saved_' + method.__name__
|
||||
attr = args_and_kwargs(args, kwargs)
|
||||
setattr(self, attr_name, attr)
|
||||
return method(self, *args, **kwargs)
|
||||
return wrapper
|
0
libs/win/jaraco/structures/__init__.py
Normal file
0
libs/win/jaraco/structures/__init__.py
Normal file
151
libs/win/jaraco/structures/binary.py
Normal file
151
libs/win/jaraco/structures/binary.py
Normal file
|
@ -0,0 +1,151 @@
|
|||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import numbers
|
||||
from functools import reduce
|
||||
|
||||
|
||||
def get_bit_values(number, size=32):
|
||||
"""
|
||||
Get bit values as a list for a given number
|
||||
|
||||
>>> get_bit_values(1) == [0]*31 + [1]
|
||||
True
|
||||
|
||||
>>> get_bit_values(0xDEADBEEF)
|
||||
[1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1]
|
||||
|
||||
You may override the default word size of 32-bits to match your actual
|
||||
application.
|
||||
|
||||
>>> get_bit_values(0x3, 2)
|
||||
[1, 1]
|
||||
|
||||
>>> get_bit_values(0x3, 4)
|
||||
[0, 0, 1, 1]
|
||||
"""
|
||||
number += 2**size
|
||||
return list(map(int, bin(number)[-size:]))
|
||||
|
||||
|
||||
def gen_bit_values(number):
|
||||
"""
|
||||
Return a zero or one for each bit of a numeric value up to the most
|
||||
significant 1 bit, beginning with the least significant bit.
|
||||
|
||||
>>> list(gen_bit_values(16))
|
||||
[0, 0, 0, 0, 1]
|
||||
"""
|
||||
digits = bin(number)[2:]
|
||||
return map(int, reversed(digits))
|
||||
|
||||
|
||||
def coalesce(bits):
|
||||
"""
|
||||
Take a sequence of bits, most significant first, and
|
||||
coalesce them into a number.
|
||||
|
||||
>>> coalesce([1,0,1])
|
||||
5
|
||||
"""
|
||||
operation = lambda a, b: (a << 1 | b)
|
||||
return reduce(operation, bits)
|
||||
|
||||
|
||||
class Flags(object):
|
||||
"""
|
||||
Subclasses should define _names, a list of flag names beginning
|
||||
with the least-significant bit.
|
||||
|
||||
>>> class MyFlags(Flags):
|
||||
... _names = 'a', 'b', 'c'
|
||||
>>> mf = MyFlags.from_number(5)
|
||||
>>> mf['a']
|
||||
1
|
||||
>>> mf['b']
|
||||
0
|
||||
>>> mf['c'] == mf[2]
|
||||
True
|
||||
>>> mf['b'] = 1
|
||||
>>> mf['a'] = 0
|
||||
>>> mf.number
|
||||
6
|
||||
"""
|
||||
def __init__(self, values):
|
||||
self._values = list(values)
|
||||
if hasattr(self, '_names'):
|
||||
n_missing_bits = len(self._names) - len(self._values)
|
||||
self._values.extend([0] * n_missing_bits)
|
||||
|
||||
@classmethod
|
||||
def from_number(cls, number):
|
||||
return cls(gen_bit_values(number))
|
||||
|
||||
@property
|
||||
def number(self):
|
||||
return coalesce(reversed(self._values))
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
# first try by index, then by name
|
||||
try:
|
||||
self._values[key] = value
|
||||
except TypeError:
|
||||
index = self._names.index(key)
|
||||
self._values[index] = value
|
||||
|
||||
def __getitem__(self, key):
|
||||
# first try by index, then by name
|
||||
try:
|
||||
return self._values[key]
|
||||
except TypeError:
|
||||
index = self._names.index(key)
|
||||
return self._values[index]
|
||||
|
||||
|
||||
class BitMask(type):
|
||||
"""
|
||||
A metaclass to create a bitmask with attributes. Subclass an int and
|
||||
set this as the metaclass to use.
|
||||
|
||||
Here's how to create such a class on Python 3:
|
||||
|
||||
class MyBits(int, metaclass=BitMask):
|
||||
a = 0x1
|
||||
b = 0x4
|
||||
c = 0x3
|
||||
|
||||
For testing purposes, construct explicitly to support Python 2
|
||||
|
||||
>>> ns = dict(a=0x1, b=0x4, c=0x3)
|
||||
>>> MyBits = BitMask(str('MyBits'), (int,), ns)
|
||||
|
||||
>>> b1 = MyBits(3)
|
||||
>>> b1.a, b1.b, b1.c
|
||||
(True, False, True)
|
||||
>>> b2 = MyBits(8)
|
||||
>>> any([b2.a, b2.b, b2.c])
|
||||
False
|
||||
|
||||
If the instance defines methods, they won't be wrapped in
|
||||
properties.
|
||||
|
||||
>>> ns['get_value'] = classmethod(lambda cls: 'some value')
|
||||
>>> ns['prop'] = property(lambda self: 'a property')
|
||||
>>> MyBits = BitMask(str('MyBits'), (int,), ns)
|
||||
|
||||
>>> MyBits(3).get_value()
|
||||
'some value'
|
||||
>>> MyBits(3).prop
|
||||
'a property'
|
||||
"""
|
||||
|
||||
def __new__(cls, name, bases, attrs):
|
||||
def make_property(name, value):
|
||||
if name.startswith('_') or not isinstance(value, numbers.Number):
|
||||
return value
|
||||
return property(lambda self, value=value: bool(self & value))
|
||||
|
||||
newattrs = dict(
|
||||
(name, make_property(name, value))
|
||||
for name, value in attrs.items()
|
||||
)
|
||||
return type.__new__(cls, name, bases, newattrs)
|
452
libs/win/jaraco/text.py
Normal file
452
libs/win/jaraco/text.py
Normal file
|
@ -0,0 +1,452 @@
|
|||
from __future__ import absolute_import, unicode_literals, print_function
|
||||
|
||||
import sys
|
||||
import re
|
||||
import inspect
|
||||
import itertools
|
||||
import textwrap
|
||||
import functools
|
||||
|
||||
import six
|
||||
|
||||
import jaraco.collections
|
||||
from jaraco.functools import compose
|
||||
|
||||
|
||||
def substitution(old, new):
|
||||
"""
|
||||
Return a function that will perform a substitution on a string
|
||||
"""
|
||||
return lambda s: s.replace(old, new)
|
||||
|
||||
|
||||
def multi_substitution(*substitutions):
|
||||
"""
|
||||
Take a sequence of pairs specifying substitutions, and create
|
||||
a function that performs those substitutions.
|
||||
|
||||
>>> multi_substitution(('foo', 'bar'), ('bar', 'baz'))('foo')
|
||||
'baz'
|
||||
"""
|
||||
substitutions = itertools.starmap(substitution, substitutions)
|
||||
# compose function applies last function first, so reverse the
|
||||
# substitutions to get the expected order.
|
||||
substitutions = reversed(tuple(substitutions))
|
||||
return compose(*substitutions)
|
||||
|
||||
|
||||
class FoldedCase(six.text_type):
|
||||
"""
|
||||
A case insensitive string class; behaves just like str
|
||||
except compares equal when the only variation is case.
|
||||
|
||||
>>> s = FoldedCase('hello world')
|
||||
|
||||
>>> s == 'Hello World'
|
||||
True
|
||||
|
||||
>>> 'Hello World' == s
|
||||
True
|
||||
|
||||
>>> s != 'Hello World'
|
||||
False
|
||||
|
||||
>>> s.index('O')
|
||||
4
|
||||
|
||||
>>> s.split('O')
|
||||
['hell', ' w', 'rld']
|
||||
|
||||
>>> sorted(map(FoldedCase, ['GAMMA', 'alpha', 'Beta']))
|
||||
['alpha', 'Beta', 'GAMMA']
|
||||
|
||||
Sequence membership is straightforward.
|
||||
|
||||
>>> "Hello World" in [s]
|
||||
True
|
||||
>>> s in ["Hello World"]
|
||||
True
|
||||
|
||||
You may test for set inclusion, but candidate and elements
|
||||
must both be folded.
|
||||
|
||||
>>> FoldedCase("Hello World") in {s}
|
||||
True
|
||||
>>> s in {FoldedCase("Hello World")}
|
||||
True
|
||||
|
||||
String inclusion works as long as the FoldedCase object
|
||||
is on the right.
|
||||
|
||||
>>> "hello" in FoldedCase("Hello World")
|
||||
True
|
||||
|
||||
But not if the FoldedCase object is on the left:
|
||||
|
||||
>>> FoldedCase('hello') in 'Hello World'
|
||||
False
|
||||
|
||||
In that case, use in_:
|
||||
|
||||
>>> FoldedCase('hello').in_('Hello World')
|
||||
True
|
||||
|
||||
"""
|
||||
def __lt__(self, other):
|
||||
return self.lower() < other.lower()
|
||||
|
||||
def __gt__(self, other):
|
||||
return self.lower() > other.lower()
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.lower() == other.lower()
|
||||
|
||||
def __ne__(self, other):
|
||||
return self.lower() != other.lower()
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.lower())
|
||||
|
||||
def __contains__(self, other):
|
||||
return super(FoldedCase, self).lower().__contains__(other.lower())
|
||||
|
||||
def in_(self, other):
|
||||
"Does self appear in other?"
|
||||
return self in FoldedCase(other)
|
||||
|
||||
# cache lower since it's likely to be called frequently.
|
||||
@jaraco.functools.method_cache
|
||||
def lower(self):
|
||||
return super(FoldedCase, self).lower()
|
||||
|
||||
def index(self, sub):
|
||||
return self.lower().index(sub.lower())
|
||||
|
||||
def split(self, splitter=' ', maxsplit=0):
|
||||
pattern = re.compile(re.escape(splitter), re.I)
|
||||
return pattern.split(self, maxsplit)
|
||||
|
||||
|
||||
def local_format(string):
|
||||
"""
|
||||
format the string using variables in the caller's local namespace.
|
||||
|
||||
>>> a = 3
|
||||
>>> local_format("{a:5}")
|
||||
' 3'
|
||||
"""
|
||||
context = inspect.currentframe().f_back.f_locals
|
||||
if sys.version_info < (3, 2):
|
||||
return string.format(**context)
|
||||
return string.format_map(context)
|
||||
|
||||
|
||||
def global_format(string):
|
||||
"""
|
||||
format the string using variables in the caller's global namespace.
|
||||
|
||||
>>> a = 3
|
||||
>>> fmt = "The func name: {global_format.__name__}"
|
||||
>>> global_format(fmt)
|
||||
'The func name: global_format'
|
||||
"""
|
||||
context = inspect.currentframe().f_back.f_globals
|
||||
if sys.version_info < (3, 2):
|
||||
return string.format(**context)
|
||||
return string.format_map(context)
|
||||
|
||||
|
||||
def namespace_format(string):
|
||||
"""
|
||||
Format the string using variable in the caller's scope (locals + globals).
|
||||
|
||||
>>> a = 3
|
||||
>>> fmt = "A is {a} and this func is {namespace_format.__name__}"
|
||||
>>> namespace_format(fmt)
|
||||
'A is 3 and this func is namespace_format'
|
||||
"""
|
||||
context = jaraco.collections.DictStack()
|
||||
context.push(inspect.currentframe().f_back.f_globals)
|
||||
context.push(inspect.currentframe().f_back.f_locals)
|
||||
if sys.version_info < (3, 2):
|
||||
return string.format(**context)
|
||||
return string.format_map(context)
|
||||
|
||||
|
||||
def is_decodable(value):
|
||||
r"""
|
||||
Return True if the supplied value is decodable (using the default
|
||||
encoding).
|
||||
|
||||
>>> is_decodable(b'\xff')
|
||||
False
|
||||
>>> is_decodable(b'\x32')
|
||||
True
|
||||
"""
|
||||
# TODO: This code could be expressed more consisely and directly
|
||||
# with a jaraco.context.ExceptionTrap, but that adds an unfortunate
|
||||
# long dependency tree, so for now, use boolean literals.
|
||||
try:
|
||||
value.decode()
|
||||
except UnicodeDecodeError:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def is_binary(value):
|
||||
"""
|
||||
Return True if the value appears to be binary (that is, it's a byte
|
||||
string and isn't decodable).
|
||||
"""
|
||||
return isinstance(value, bytes) and not is_decodable(value)
|
||||
|
||||
|
||||
def trim(s):
|
||||
r"""
|
||||
Trim something like a docstring to remove the whitespace that
|
||||
is common due to indentation and formatting.
|
||||
|
||||
>>> trim("\n\tfoo = bar\n\t\tbar = baz\n")
|
||||
'foo = bar\n\tbar = baz'
|
||||
"""
|
||||
return textwrap.dedent(s).strip()
|
||||
|
||||
|
||||
class Splitter(object):
|
||||
"""object that will split a string with the given arguments for each call
|
||||
|
||||
>>> s = Splitter(',')
|
||||
>>> s('hello, world, this is your, master calling')
|
||||
['hello', ' world', ' this is your', ' master calling']
|
||||
"""
|
||||
def __init__(self, *args):
|
||||
self.args = args
|
||||
|
||||
def __call__(self, s):
|
||||
return s.split(*self.args)
|
||||
|
||||
|
||||
def indent(string, prefix=' ' * 4):
|
||||
return prefix + string
|
||||
|
||||
|
||||
class WordSet(tuple):
|
||||
"""
|
||||
Given a Python identifier, return the words that identifier represents,
|
||||
whether in camel case, underscore-separated, etc.
|
||||
|
||||
>>> WordSet.parse("camelCase")
|
||||
('camel', 'Case')
|
||||
|
||||
>>> WordSet.parse("under_sep")
|
||||
('under', 'sep')
|
||||
|
||||
Acronyms should be retained
|
||||
|
||||
>>> WordSet.parse("firstSNL")
|
||||
('first', 'SNL')
|
||||
|
||||
>>> WordSet.parse("you_and_I")
|
||||
('you', 'and', 'I')
|
||||
|
||||
>>> WordSet.parse("A simple test")
|
||||
('A', 'simple', 'test')
|
||||
|
||||
Multiple caps should not interfere with the first cap of another word.
|
||||
|
||||
>>> WordSet.parse("myABCClass")
|
||||
('my', 'ABC', 'Class')
|
||||
|
||||
The result is a WordSet, so you can get the form you need.
|
||||
|
||||
>>> WordSet.parse("myABCClass").underscore_separated()
|
||||
'my_ABC_Class'
|
||||
|
||||
>>> WordSet.parse('a-command').camel_case()
|
||||
'ACommand'
|
||||
|
||||
>>> WordSet.parse('someIdentifier').lowered().space_separated()
|
||||
'some identifier'
|
||||
|
||||
Slices of the result should return another WordSet.
|
||||
|
||||
>>> WordSet.parse('taken-out-of-context')[1:].underscore_separated()
|
||||
'out_of_context'
|
||||
|
||||
>>> WordSet.from_class_name(WordSet()).lowered().space_separated()
|
||||
'word set'
|
||||
"""
|
||||
_pattern = re.compile('([A-Z]?[a-z]+)|([A-Z]+(?![a-z]))')
|
||||
|
||||
def capitalized(self):
|
||||
return WordSet(word.capitalize() for word in self)
|
||||
|
||||
def lowered(self):
|
||||
return WordSet(word.lower() for word in self)
|
||||
|
||||
def camel_case(self):
|
||||
return ''.join(self.capitalized())
|
||||
|
||||
def headless_camel_case(self):
|
||||
words = iter(self)
|
||||
first = next(words).lower()
|
||||
return itertools.chain((first,), WordSet(words).camel_case())
|
||||
|
||||
def underscore_separated(self):
|
||||
return '_'.join(self)
|
||||
|
||||
def dash_separated(self):
|
||||
return '-'.join(self)
|
||||
|
||||
def space_separated(self):
|
||||
return ' '.join(self)
|
||||
|
||||
def __getitem__(self, item):
|
||||
result = super(WordSet, self).__getitem__(item)
|
||||
if isinstance(item, slice):
|
||||
result = WordSet(result)
|
||||
return result
|
||||
|
||||
# for compatibility with Python 2
|
||||
def __getslice__(self, i, j):
|
||||
return self.__getitem__(slice(i, j))
|
||||
|
||||
@classmethod
|
||||
def parse(cls, identifier):
|
||||
matches = cls._pattern.finditer(identifier)
|
||||
return WordSet(match.group(0) for match in matches)
|
||||
|
||||
@classmethod
|
||||
def from_class_name(cls, subject):
|
||||
return cls.parse(subject.__class__.__name__)
|
||||
|
||||
|
||||
# for backward compatibility
|
||||
words = WordSet.parse
|
||||
|
||||
|
||||
def simple_html_strip(s):
|
||||
r"""
|
||||
Remove HTML from the string `s`.
|
||||
|
||||
>>> str(simple_html_strip(''))
|
||||
''
|
||||
|
||||
>>> print(simple_html_strip('A <bold>stormy</bold> day in paradise'))
|
||||
A stormy day in paradise
|
||||
|
||||
>>> print(simple_html_strip('Somebody <!-- do not --> tell the truth.'))
|
||||
Somebody tell the truth.
|
||||
|
||||
>>> print(simple_html_strip('What about<br/>\nmultiple lines?'))
|
||||
What about
|
||||
multiple lines?
|
||||
"""
|
||||
html_stripper = re.compile('(<!--.*?-->)|(<[^>]*>)|([^<]+)', re.DOTALL)
|
||||
texts = (
|
||||
match.group(3) or ''
|
||||
for match
|
||||
in html_stripper.finditer(s)
|
||||
)
|
||||
return ''.join(texts)
|
||||
|
||||
|
||||
class SeparatedValues(six.text_type):
|
||||
"""
|
||||
A string separated by a separator. Overrides __iter__ for getting
|
||||
the values.
|
||||
|
||||
>>> list(SeparatedValues('a,b,c'))
|
||||
['a', 'b', 'c']
|
||||
|
||||
Whitespace is stripped and empty values are discarded.
|
||||
|
||||
>>> list(SeparatedValues(' a, b , c, '))
|
||||
['a', 'b', 'c']
|
||||
"""
|
||||
separator = ','
|
||||
|
||||
def __iter__(self):
|
||||
parts = self.split(self.separator)
|
||||
return six.moves.filter(None, (part.strip() for part in parts))
|
||||
|
||||
|
||||
class Stripper:
|
||||
r"""
|
||||
Given a series of lines, find the common prefix and strip it from them.
|
||||
|
||||
>>> lines = [
|
||||
... 'abcdefg\n',
|
||||
... 'abc\n',
|
||||
... 'abcde\n',
|
||||
... ]
|
||||
>>> res = Stripper.strip_prefix(lines)
|
||||
>>> res.prefix
|
||||
'abc'
|
||||
>>> list(res.lines)
|
||||
['defg\n', '\n', 'de\n']
|
||||
|
||||
If no prefix is common, nothing should be stripped.
|
||||
|
||||
>>> lines = [
|
||||
... 'abcd\n',
|
||||
... '1234\n',
|
||||
... ]
|
||||
>>> res = Stripper.strip_prefix(lines)
|
||||
>>> res.prefix = ''
|
||||
>>> list(res.lines)
|
||||
['abcd\n', '1234\n']
|
||||
"""
|
||||
def __init__(self, prefix, lines):
|
||||
self.prefix = prefix
|
||||
self.lines = map(self, lines)
|
||||
|
||||
@classmethod
|
||||
def strip_prefix(cls, lines):
|
||||
prefix_lines, lines = itertools.tee(lines)
|
||||
prefix = functools.reduce(cls.common_prefix, prefix_lines)
|
||||
return cls(prefix, lines)
|
||||
|
||||
def __call__(self, line):
|
||||
if not self.prefix:
|
||||
return line
|
||||
null, prefix, rest = line.partition(self.prefix)
|
||||
return rest
|
||||
|
||||
@staticmethod
|
||||
def common_prefix(s1, s2):
|
||||
"""
|
||||
Return the common prefix of two lines.
|
||||
"""
|
||||
index = min(len(s1), len(s2))
|
||||
while s1[:index] != s2[:index]:
|
||||
index -= 1
|
||||
return s1[:index]
|
||||
|
||||
|
||||
def remove_prefix(text, prefix):
|
||||
"""
|
||||
Remove the prefix from the text if it exists.
|
||||
|
||||
>>> remove_prefix('underwhelming performance', 'underwhelming ')
|
||||
'performance'
|
||||
|
||||
>>> remove_prefix('something special', 'sample')
|
||||
'something special'
|
||||
"""
|
||||
null, prefix, rest = text.rpartition(prefix)
|
||||
return rest
|
||||
|
||||
|
||||
def remove_suffix(text, suffix):
|
||||
"""
|
||||
Remove the suffix from the text if it exists.
|
||||
|
||||
>>> remove_suffix('name.git', '.git')
|
||||
'name'
|
||||
|
||||
>>> remove_suffix('something special', 'sample')
|
||||
'something special'
|
||||
"""
|
||||
rest, suffix, null = text.partition(suffix)
|
||||
return rest
|
0
libs/win/jaraco/ui/__init__.py
Normal file
0
libs/win/jaraco/ui/__init__.py
Normal file
77
libs/win/jaraco/ui/cmdline.py
Normal file
77
libs/win/jaraco/ui/cmdline.py
Normal file
|
@ -0,0 +1,77 @@
|
|||
import argparse
|
||||
|
||||
import six
|
||||
from jaraco.classes import meta
|
||||
from jaraco import text
|
||||
|
||||
|
||||
@six.add_metaclass(meta.LeafClassesMeta)
|
||||
class Command(object):
|
||||
"""
|
||||
A general-purpose base class for creating commands for a command-line
|
||||
program using argparse. Each subclass of Command represents a separate
|
||||
sub-command of a program.
|
||||
|
||||
For example, one might use Command subclasses to implement the Mercurial
|
||||
command set::
|
||||
|
||||
class Commit(Command):
|
||||
@staticmethod
|
||||
def add_arguments(cls, parser):
|
||||
parser.add_argument('-m', '--message')
|
||||
|
||||
@classmethod
|
||||
def run(cls, args):
|
||||
"Run the 'commit' command with args (parsed)"
|
||||
|
||||
class Merge(Command): pass
|
||||
class Pull(Command): pass
|
||||
...
|
||||
|
||||
Then one could create an entry point for Mercurial like so::
|
||||
|
||||
def hg_command():
|
||||
Command.invoke()
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def add_subparsers(cls, parser):
|
||||
subparsers = parser.add_subparsers()
|
||||
[cmd_class.add_parser(subparsers) for cmd_class in cls._leaf_classes]
|
||||
|
||||
@classmethod
|
||||
def add_parser(cls, subparsers):
|
||||
cmd_string = text.words(cls.__name__).lowered().dash_separated()
|
||||
parser = subparsers.add_parser(cmd_string)
|
||||
parser.set_defaults(action=cls)
|
||||
cls.add_arguments(parser)
|
||||
return parser
|
||||
|
||||
@classmethod
|
||||
def add_arguments(cls, parser):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def invoke(cls):
|
||||
"""
|
||||
Invoke the command using ArgumentParser
|
||||
"""
|
||||
parser = argparse.ArgumentParser()
|
||||
cls.add_subparsers(parser)
|
||||
args = parser.parse_args()
|
||||
args.action.run(args)
|
||||
|
||||
|
||||
class Extend(argparse.Action):
|
||||
"""
|
||||
Argparse action to take an nargs=* argument
|
||||
and add any values to the existing value.
|
||||
|
||||
>>> parser = argparse.ArgumentParser()
|
||||
>>> _ = parser.add_argument('--foo', nargs='*', default=[], action=Extend)
|
||||
>>> args = parser.parse_args(['--foo', 'a=1', '--foo', 'b=2', 'c=3'])
|
||||
>>> args.foo
|
||||
['a=1', 'b=2', 'c=3']
|
||||
"""
|
||||
def __call__(self, parser, namespace, values, option_string=None):
|
||||
getattr(namespace, self.dest).extend(values)
|
108
libs/win/jaraco/ui/editor.py
Normal file
108
libs/win/jaraco/ui/editor.py
Normal file
|
@ -0,0 +1,108 @@
|
|||
from __future__ import unicode_literals, absolute_import
|
||||
|
||||
import tempfile
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
import mimetypes
|
||||
import collections
|
||||
import io
|
||||
import difflib
|
||||
|
||||
import six
|
||||
|
||||
class EditProcessException(RuntimeError): pass
|
||||
|
||||
class EditableFile(object):
|
||||
"""
|
||||
EditableFile saves some data to a temporary file, launches a
|
||||
platform editor for interactive editing, and then reloads the data,
|
||||
setting .changed to True if the data was edited.
|
||||
|
||||
e.g.::
|
||||
|
||||
x = EditableFile('foo')
|
||||
x.edit()
|
||||
|
||||
if x.changed:
|
||||
print(x.data)
|
||||
|
||||
The EDITOR environment variable can define which executable to use
|
||||
(also XML_EDITOR if the content-type to edit includes 'xml'). If no
|
||||
EDITOR is defined, defaults to 'notepad' on Windows and 'edit' on
|
||||
other platforms.
|
||||
"""
|
||||
platform_default_editors = collections.defaultdict(
|
||||
lambda: 'edit',
|
||||
win32 = 'notepad',
|
||||
linux2 = 'vi',
|
||||
)
|
||||
encoding = 'utf-8'
|
||||
|
||||
def __init__(self, data='', content_type='text/plain'):
|
||||
self.data = six.text_type(data)
|
||||
self.content_type = content_type
|
||||
|
||||
def __enter__(self):
|
||||
extension = mimetypes.guess_extension(self.content_type) or ''
|
||||
fobj, self.name = tempfile.mkstemp(extension)
|
||||
os.write(fobj, self.data.encode(self.encoding))
|
||||
os.close(fobj)
|
||||
return self
|
||||
|
||||
def read(self):
|
||||
with open(self.name, 'rb') as f:
|
||||
return f.read().decode(self.encoding)
|
||||
|
||||
def __exit__(self, *tb_info):
|
||||
os.remove(self.name)
|
||||
|
||||
def edit(self):
|
||||
"""
|
||||
Edit the file
|
||||
"""
|
||||
self.changed = False
|
||||
with self:
|
||||
editor = self.get_editor()
|
||||
cmd = [editor, self.name]
|
||||
try:
|
||||
res = subprocess.call(cmd)
|
||||
except Exception as e:
|
||||
print("Error launching editor %(editor)s" % locals())
|
||||
print(e)
|
||||
return
|
||||
if res != 0:
|
||||
msg = '%(editor)s returned error status %(res)d' % locals()
|
||||
raise EditProcessException(msg)
|
||||
new_data = self.read()
|
||||
if new_data != self.data:
|
||||
self.changed = self._save_diff(self.data, new_data)
|
||||
self.data = new_data
|
||||
|
||||
@staticmethod
|
||||
def _search_env(keys):
|
||||
"""
|
||||
Search the environment for the supplied keys, returning the first
|
||||
one found or None if none was found.
|
||||
"""
|
||||
matches = (os.environ[key] for key in keys if key in os.environ)
|
||||
return next(matches, None)
|
||||
|
||||
def get_editor(self):
|
||||
"""
|
||||
Give preference to an XML_EDITOR or EDITOR defined in the
|
||||
environment. Otherwise use a default editor based on platform.
|
||||
"""
|
||||
env_search = ['EDITOR']
|
||||
if 'xml' in self.content_type:
|
||||
env_search.insert(0, 'XML_EDITOR')
|
||||
default_editor = self.platform_default_editors[sys.platform]
|
||||
return self._search_env(env_search) or default_editor
|
||||
|
||||
@staticmethod
|
||||
def _save_diff(*versions):
|
||||
def get_lines(content):
|
||||
return list(io.StringIO(content))
|
||||
lines = map(get_lines, versions)
|
||||
diff = difflib.context_diff(*lines)
|
||||
return tuple(diff)
|
26
libs/win/jaraco/ui/input.py
Normal file
26
libs/win/jaraco/ui/input.py
Normal file
|
@ -0,0 +1,26 @@
|
|||
"""
|
||||
This module currently provides a cross-platform getch function
|
||||
"""
|
||||
|
||||
try:
|
||||
# Windows
|
||||
from msvcrt import getch
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
try:
|
||||
# Unix
|
||||
import sys
|
||||
import tty
|
||||
import termios
|
||||
|
||||
def getch():
|
||||
fd = sys.stdin.fileno()
|
||||
old = termios.tcgetattr(fd)
|
||||
try:
|
||||
tty.setraw(fd)
|
||||
return sys.stdin.read(1)
|
||||
finally:
|
||||
termios.tcsetattr(fd, termios.TCSADRAIN, old)
|
||||
except ImportError:
|
||||
pass
|
34
libs/win/jaraco/ui/menu.py
Normal file
34
libs/win/jaraco/ui/menu.py
Normal file
|
@ -0,0 +1,34 @@
|
|||
from __future__ import print_function, absolute_import, unicode_literals
|
||||
|
||||
import itertools
|
||||
|
||||
import six
|
||||
|
||||
class Menu(object):
|
||||
"""
|
||||
A simple command-line based menu
|
||||
"""
|
||||
def __init__(self, choices=None, formatter=str):
|
||||
self.choices = choices or list()
|
||||
self.formatter = formatter
|
||||
|
||||
def get_choice(self, prompt="> "):
|
||||
n = len(self.choices)
|
||||
number_width = len(str(n)) + 1
|
||||
menu_fmt = '{number:{number_width}}) {choice}'
|
||||
formatted_choices = map(self.formatter, self.choices)
|
||||
for number, choice in zip(itertools.count(1), formatted_choices):
|
||||
print(menu_fmt.format(**locals()))
|
||||
print()
|
||||
try:
|
||||
answer = int(six.moves.input(prompt))
|
||||
result = self.choices[answer - 1]
|
||||
except ValueError:
|
||||
print('invalid selection')
|
||||
result = None
|
||||
except IndexError:
|
||||
print('invalid selection')
|
||||
result = None
|
||||
except KeyboardInterrupt:
|
||||
result = None
|
||||
return result
|
152
libs/win/jaraco/ui/progress.py
Normal file
152
libs/win/jaraco/ui/progress.py
Normal file
|
@ -0,0 +1,152 @@
|
|||
# deprecated -- use TQDM
|
||||
|
||||
from __future__ import (print_function, absolute_import, unicode_literals,
|
||||
division)
|
||||
|
||||
import time
|
||||
import sys
|
||||
import itertools
|
||||
import abc
|
||||
import datetime
|
||||
|
||||
import six
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class AbstractProgressBar(object):
|
||||
def __init__(self, unit='', size=70):
|
||||
"""
|
||||
Size is the nominal size in characters
|
||||
"""
|
||||
self.unit = unit
|
||||
self.size = size
|
||||
|
||||
def report(self, amt):
|
||||
sys.stdout.write('\r%s' % self.get_bar(amt))
|
||||
sys.stdout.flush()
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_bar(self, amt):
|
||||
"Return the string to be printed. Should be size >= self.size"
|
||||
|
||||
def summary(self, str):
|
||||
return ' (' + self.unit_str(str) + ')'
|
||||
|
||||
def unit_str(self, str):
|
||||
if self.unit:
|
||||
str += ' ' + self.unit
|
||||
return str
|
||||
|
||||
def finish(self):
|
||||
print()
|
||||
|
||||
def __enter__(self):
|
||||
self.report(0)
|
||||
return self
|
||||
|
||||
def __exit__(self, exc, exc_val, tb):
|
||||
if exc is None:
|
||||
self.finish()
|
||||
else:
|
||||
print()
|
||||
|
||||
def iterate(self, iterable):
|
||||
"""
|
||||
Report the status as the iterable is consumed.
|
||||
"""
|
||||
with self:
|
||||
for n, item in enumerate(iterable, 1):
|
||||
self.report(n)
|
||||
yield item
|
||||
|
||||
|
||||
class SimpleProgressBar(AbstractProgressBar):
|
||||
|
||||
_PROG_DISPGLYPH = itertools.cycle(['|', '/', '-', '\\'])
|
||||
|
||||
def get_bar(self, amt):
|
||||
bar = next(self._PROG_DISPGLYPH)
|
||||
template = ' [{bar:^{bar_len}}]'
|
||||
summary = self.summary('{amt}')
|
||||
template += summary
|
||||
empty = template.format(
|
||||
bar='',
|
||||
bar_len=0,
|
||||
amt=amt,
|
||||
)
|
||||
bar_len = self.size - len(empty)
|
||||
return template.format(**locals())
|
||||
|
||||
@classmethod
|
||||
def demo(cls):
|
||||
bar3 = cls(unit='cubes', size=30)
|
||||
with bar3:
|
||||
for x in six.moves.range(1, 759):
|
||||
bar3.report(x)
|
||||
time.sleep(0.01)
|
||||
|
||||
|
||||
class TargetProgressBar(AbstractProgressBar):
|
||||
def __init__(self, total=None, unit='', size=70):
|
||||
"""
|
||||
Size is the nominal size in characters
|
||||
"""
|
||||
self.total = total
|
||||
super(TargetProgressBar, self).__init__(unit, size)
|
||||
|
||||
def get_bar(self, amt):
|
||||
template = ' [{bar:<{bar_len}}]'
|
||||
completed = amt / self.total
|
||||
percent = int(completed * 100)
|
||||
percent_str = ' {percent:3}%'
|
||||
template += percent_str
|
||||
summary = self.summary('{amt}/{total}')
|
||||
template += summary
|
||||
empty = template.format(
|
||||
total=self.total,
|
||||
bar='',
|
||||
bar_len=0,
|
||||
**locals()
|
||||
)
|
||||
bar_len = self.size - len(empty)
|
||||
bar = '=' * int(completed * bar_len)
|
||||
return template.format(total=self.total, **locals())
|
||||
|
||||
@classmethod
|
||||
def demo(cls):
|
||||
bar1 = cls(100, 'blocks')
|
||||
with bar1:
|
||||
for x in six.moves.range(1, 101):
|
||||
bar1.report(x)
|
||||
time.sleep(0.05)
|
||||
|
||||
bar2 = cls(758, size=50)
|
||||
with bar2:
|
||||
for x in six.moves.range(1, 759):
|
||||
bar2.report(x)
|
||||
time.sleep(0.01)
|
||||
|
||||
def finish(self):
|
||||
self.report(self.total)
|
||||
super(TargetProgressBar, self).finish()
|
||||
|
||||
|
||||
def countdown(template, duration=datetime.timedelta(seconds=5)):
|
||||
"""
|
||||
Do a countdown for duration, printing the template (which may accept one
|
||||
positional argument). Template should be something like
|
||||
``countdown complete in {} seconds.``
|
||||
"""
|
||||
now = datetime.datetime.now()
|
||||
deadline = now + duration
|
||||
remaining = deadline - datetime.datetime.now()
|
||||
while remaining:
|
||||
remaining = deadline - datetime.datetime.now()
|
||||
remaining = max(datetime.timedelta(), remaining)
|
||||
msg = template.format(remaining.total_seconds())
|
||||
print(msg, end=' '*10)
|
||||
sys.stdout.flush()
|
||||
time.sleep(.1)
|
||||
print('\b'*80, end='')
|
||||
sys.stdout.flush()
|
||||
print()
|
13
libs/win/jaraco/windows/__init__.py
Normal file
13
libs/win/jaraco/windows/__init__.py
Normal file
|
@ -0,0 +1,13 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
# $Id$
|
||||
|
||||
"""
|
||||
jaraco.windows
|
||||
|
||||
A lightweight wrapper to provide a pythonic API to the Windows Platform.
|
||||
|
||||
This package attempts to provide interfaces similar or compatible
|
||||
with Mark Hammond's pywin32 library, but avoids the use of extension
|
||||
modules by utilizing ctypes.
|
||||
"""
|
0
libs/win/jaraco/windows/api/__init__.py
Normal file
0
libs/win/jaraco/windows/api/__init__.py
Normal file
53
libs/win/jaraco/windows/api/clipboard.py
Normal file
53
libs/win/jaraco/windows/api/clipboard.py
Normal file
|
@ -0,0 +1,53 @@
|
|||
import ctypes.wintypes
|
||||
|
||||
# Clipboard Formats
|
||||
CF_TEXT = 1
|
||||
CF_BITMAP = 2
|
||||
CF_METAFILEPICT = 3
|
||||
CF_SYLK = 4
|
||||
CF_DIF = 5
|
||||
CF_TIFF = 6
|
||||
CF_OEMTEXT = 7
|
||||
CF_DIB = 8
|
||||
CF_PALETTE = 9
|
||||
CF_PENDATA = 10
|
||||
CF_RIFF = 11
|
||||
CF_WAVE = 12
|
||||
CF_UNICODETEXT = 13
|
||||
CF_ENHMETAFILE = 14
|
||||
CF_HDROP = 15
|
||||
CF_LOCALE = 16
|
||||
CF_DIBV5 = 17
|
||||
CF_MAX = 18
|
||||
CF_OWNERDISPLAY = 0x0080
|
||||
CF_DSPTEXT = 0x0081
|
||||
CF_DSPBITMAP = 0x0082
|
||||
CF_DSPMETAFILEPICT = 0x0083
|
||||
CF_DSPENHMETAFILE = 0x008E
|
||||
CF_PRIVATEFIRST = 0x0200
|
||||
CF_PRIVATELAST = 0x02FF
|
||||
CF_GDIOBJFIRST = 0x0300
|
||||
CF_GDIOBJLAST = 0x03FF
|
||||
|
||||
RegisterClipboardFormat = ctypes.windll.user32.RegisterClipboardFormatW
|
||||
RegisterClipboardFormat.argtypes = ctypes.wintypes.LPWSTR,
|
||||
RegisterClipboardFormat.restype = ctypes.wintypes.UINT
|
||||
CF_HTML = RegisterClipboardFormat('HTML Format')
|
||||
|
||||
EnumClipboardFormats = ctypes.windll.user32.EnumClipboardFormats
|
||||
EnumClipboardFormats.argtypes = ctypes.wintypes.UINT,
|
||||
EnumClipboardFormats.restype = ctypes.wintypes.UINT
|
||||
|
||||
GetClipboardData = ctypes.windll.user32.GetClipboardData
|
||||
GetClipboardData.argtypes = ctypes.wintypes.UINT,
|
||||
GetClipboardData.restype = ctypes.wintypes.HANDLE
|
||||
|
||||
SetClipboardData = ctypes.windll.user32.SetClipboardData
|
||||
SetClipboardData.argtypes = ctypes.wintypes.UINT, ctypes.wintypes.HANDLE
|
||||
SetClipboardData.restype = ctypes.wintypes.HANDLE
|
||||
|
||||
OpenClipboard = ctypes.windll.user32.OpenClipboard
|
||||
OpenClipboard.argtypes = ctypes.wintypes.HANDLE,
|
||||
OpenClipboard.restype = ctypes.wintypes.BOOL
|
||||
|
||||
ctypes.windll.user32.CloseClipboard.restype = ctypes.wintypes.BOOL
|
62
libs/win/jaraco/windows/api/credential.py
Normal file
62
libs/win/jaraco/windows/api/credential.py
Normal file
|
@ -0,0 +1,62 @@
|
|||
"""
|
||||
Support for Credential Vault
|
||||
"""
|
||||
|
||||
import ctypes
|
||||
from ctypes.wintypes import DWORD, LPCWSTR, BOOL, LPWSTR, FILETIME
|
||||
|
||||
try:
|
||||
from ctypes.wintypes import LPBYTE
|
||||
except ImportError:
|
||||
LPBYTE = ctypes.POINTER(ctypes.wintypes.BYTE)
|
||||
|
||||
|
||||
class CredentialAttribute(ctypes.Structure):
|
||||
_fields_ = []
|
||||
|
||||
|
||||
class Credential(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('flags', DWORD),
|
||||
('type', DWORD),
|
||||
('target_name', LPWSTR),
|
||||
('comment', LPWSTR),
|
||||
('last_written', FILETIME),
|
||||
('credential_blob_size', DWORD),
|
||||
('credential_blob', LPBYTE),
|
||||
('persist', DWORD),
|
||||
('attribute_count', DWORD),
|
||||
('attributes', ctypes.POINTER(CredentialAttribute)),
|
||||
('target_alias', LPWSTR),
|
||||
('user_name', LPWSTR),
|
||||
]
|
||||
|
||||
def __del__(self):
|
||||
ctypes.windll.advapi32.CredFree(ctypes.byref(self))
|
||||
|
||||
|
||||
PCREDENTIAL = ctypes.POINTER(Credential)
|
||||
|
||||
CredRead = ctypes.windll.advapi32.CredReadW
|
||||
CredRead.argtypes = (
|
||||
LPCWSTR, # TargetName
|
||||
DWORD, # Type
|
||||
DWORD, # Flags
|
||||
ctypes.POINTER(PCREDENTIAL), # Credential
|
||||
)
|
||||
CredRead.restype = BOOL
|
||||
|
||||
CredWrite = ctypes.windll.advapi32.CredWriteW
|
||||
CredWrite.argtypes = (
|
||||
PCREDENTIAL, # Credential
|
||||
DWORD, # Flags
|
||||
)
|
||||
CredWrite.restype = BOOL
|
||||
|
||||
CredDelete = ctypes.windll.advapi32.CredDeleteW
|
||||
CredDelete.argtypes = (
|
||||
LPCWSTR, # TargetName
|
||||
DWORD, # Type
|
||||
DWORD, # Flags
|
||||
)
|
||||
CredDelete.restype = BOOL
|
13
libs/win/jaraco/windows/api/environ.py
Normal file
13
libs/win/jaraco/windows/api/environ.py
Normal file
|
@ -0,0 +1,13 @@
|
|||
import ctypes.wintypes
|
||||
|
||||
SetEnvironmentVariable = ctypes.windll.kernel32.SetEnvironmentVariableW
|
||||
SetEnvironmentVariable.restype = ctypes.wintypes.BOOL
|
||||
SetEnvironmentVariable.argtypes = [ctypes.wintypes.LPCWSTR] * 2
|
||||
|
||||
GetEnvironmentVariable = ctypes.windll.kernel32.GetEnvironmentVariableW
|
||||
GetEnvironmentVariable.restype = ctypes.wintypes.BOOL
|
||||
GetEnvironmentVariable.argtypes = [
|
||||
ctypes.wintypes.LPCWSTR,
|
||||
ctypes.wintypes.LPWSTR,
|
||||
ctypes.wintypes.DWORD,
|
||||
]
|
7
libs/win/jaraco/windows/api/errors.py
Normal file
7
libs/win/jaraco/windows/api/errors.py
Normal file
|
@ -0,0 +1,7 @@
|
|||
# from error.h
|
||||
NO_ERROR = 0
|
||||
ERROR_INSUFFICIENT_BUFFER = 122
|
||||
ERROR_BUFFER_OVERFLOW = 111
|
||||
ERROR_NO_DATA = 232
|
||||
ERROR_INVALID_PARAMETER = 87
|
||||
ERROR_NOT_SUPPORTED = 50
|
38
libs/win/jaraco/windows/api/event.py
Normal file
38
libs/win/jaraco/windows/api/event.py
Normal file
|
@ -0,0 +1,38 @@
|
|||
from ctypes import windll, POINTER
|
||||
from ctypes.wintypes import (
|
||||
LPWSTR, DWORD, LPVOID, HANDLE, BOOL,
|
||||
)
|
||||
|
||||
CreateEvent = windll.kernel32.CreateEventW
|
||||
CreateEvent.argtypes = (
|
||||
LPVOID, # LPSECURITY_ATTRIBUTES
|
||||
BOOL,
|
||||
BOOL,
|
||||
LPWSTR,
|
||||
)
|
||||
CreateEvent.restype = HANDLE
|
||||
|
||||
SetEvent = windll.kernel32.SetEvent
|
||||
SetEvent.argtypes = HANDLE,
|
||||
SetEvent.restype = BOOL
|
||||
|
||||
WaitForSingleObject = windll.kernel32.WaitForSingleObject
|
||||
WaitForSingleObject.argtypes = HANDLE, DWORD
|
||||
WaitForSingleObject.restype = DWORD
|
||||
|
||||
_WaitForMultipleObjects = windll.kernel32.WaitForMultipleObjects
|
||||
_WaitForMultipleObjects.argtypes = DWORD, POINTER(HANDLE), BOOL, DWORD
|
||||
_WaitForMultipleObjects.restype = DWORD
|
||||
|
||||
|
||||
def WaitForMultipleObjects(handles, wait_all=False, timeout=0):
|
||||
n_handles = len(handles)
|
||||
handle_array = (HANDLE * n_handles)()
|
||||
for index, handle in enumerate(handles):
|
||||
handle_array[index] = handle
|
||||
return _WaitForMultipleObjects(n_handles, handle_array, wait_all, timeout)
|
||||
|
||||
|
||||
WAIT_OBJECT_0 = 0
|
||||
INFINITE = -1
|
||||
WAIT_TIMEOUT = 0x102
|
317
libs/win/jaraco/windows/api/filesystem.py
Normal file
317
libs/win/jaraco/windows/api/filesystem.py
Normal file
|
@ -0,0 +1,317 @@
|
|||
import ctypes.wintypes
|
||||
|
||||
CreateSymbolicLink = ctypes.windll.kernel32.CreateSymbolicLinkW
|
||||
CreateSymbolicLink.argtypes = (
|
||||
ctypes.wintypes.LPWSTR,
|
||||
ctypes.wintypes.LPWSTR,
|
||||
ctypes.wintypes.DWORD,
|
||||
)
|
||||
CreateSymbolicLink.restype = ctypes.wintypes.BOOLEAN
|
||||
|
||||
CreateHardLink = ctypes.windll.kernel32.CreateHardLinkW
|
||||
CreateHardLink.argtypes = (
|
||||
ctypes.wintypes.LPWSTR,
|
||||
ctypes.wintypes.LPWSTR,
|
||||
ctypes.wintypes.LPVOID, # reserved for LPSECURITY_ATTRIBUTES
|
||||
)
|
||||
CreateHardLink.restype = ctypes.wintypes.BOOLEAN
|
||||
|
||||
GetFileAttributes = ctypes.windll.kernel32.GetFileAttributesW
|
||||
GetFileAttributes.argtypes = ctypes.wintypes.LPWSTR,
|
||||
GetFileAttributes.restype = ctypes.wintypes.DWORD
|
||||
|
||||
SetFileAttributes = ctypes.windll.kernel32.SetFileAttributesW
|
||||
SetFileAttributes.argtypes = ctypes.wintypes.LPWSTR, ctypes.wintypes.DWORD
|
||||
SetFileAttributes.restype = ctypes.wintypes.BOOL
|
||||
|
||||
MAX_PATH = 260
|
||||
|
||||
GetFinalPathNameByHandle = ctypes.windll.kernel32.GetFinalPathNameByHandleW
|
||||
GetFinalPathNameByHandle.argtypes = (
|
||||
ctypes.wintypes.HANDLE, ctypes.wintypes.LPWSTR, ctypes.wintypes.DWORD,
|
||||
ctypes.wintypes.DWORD,
|
||||
)
|
||||
GetFinalPathNameByHandle.restype = ctypes.wintypes.DWORD
|
||||
|
||||
|
||||
class SECURITY_ATTRIBUTES(ctypes.Structure):
|
||||
_fields_ = (
|
||||
('length', ctypes.wintypes.DWORD),
|
||||
('p_security_descriptor', ctypes.wintypes.LPVOID),
|
||||
('inherit_handle', ctypes.wintypes.BOOLEAN),
|
||||
)
|
||||
|
||||
|
||||
LPSECURITY_ATTRIBUTES = ctypes.POINTER(SECURITY_ATTRIBUTES)
|
||||
|
||||
CreateFile = ctypes.windll.kernel32.CreateFileW
|
||||
CreateFile.argtypes = (
|
||||
ctypes.wintypes.LPWSTR,
|
||||
ctypes.wintypes.DWORD,
|
||||
ctypes.wintypes.DWORD,
|
||||
LPSECURITY_ATTRIBUTES,
|
||||
ctypes.wintypes.DWORD,
|
||||
ctypes.wintypes.DWORD,
|
||||
ctypes.wintypes.HANDLE,
|
||||
)
|
||||
CreateFile.restype = ctypes.wintypes.HANDLE
|
||||
FILE_SHARE_READ = 1
|
||||
FILE_SHARE_WRITE = 2
|
||||
FILE_SHARE_DELETE = 4
|
||||
FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000
|
||||
FILE_FLAG_BACKUP_SEMANTICS = 0x2000000
|
||||
NULL = 0
|
||||
OPEN_EXISTING = 3
|
||||
FILE_ATTRIBUTE_READONLY = 0x1
|
||||
FILE_ATTRIBUTE_HIDDEN = 0x2
|
||||
FILE_ATTRIBUTE_DIRECTORY = 0x10
|
||||
FILE_ATTRIBUTE_NORMAL = 0x80
|
||||
FILE_ATTRIBUTE_REPARSE_POINT = 0x400
|
||||
GENERIC_READ = 0x80000000
|
||||
FILE_READ_ATTRIBUTES = 0x80
|
||||
INVALID_HANDLE_VALUE = ctypes.wintypes.HANDLE(-1).value
|
||||
|
||||
INVALID_FILE_ATTRIBUTES = 0xFFFFFFFF
|
||||
|
||||
ERROR_NO_MORE_FILES = 0x12
|
||||
|
||||
VOLUME_NAME_DOS = 0
|
||||
|
||||
CloseHandle = ctypes.windll.kernel32.CloseHandle
|
||||
CloseHandle.argtypes = (ctypes.wintypes.HANDLE,)
|
||||
CloseHandle.restype = ctypes.wintypes.BOOLEAN
|
||||
|
||||
|
||||
class WIN32_FIND_DATA(ctypes.wintypes.WIN32_FIND_DATAW):
|
||||
"""
|
||||
_fields_ = [
|
||||
("dwFileAttributes", DWORD),
|
||||
("ftCreationTime", FILETIME),
|
||||
("ftLastAccessTime", FILETIME),
|
||||
("ftLastWriteTime", FILETIME),
|
||||
("nFileSizeHigh", DWORD),
|
||||
("nFileSizeLow", DWORD),
|
||||
("dwReserved0", DWORD),
|
||||
("dwReserved1", DWORD),
|
||||
("cFileName", WCHAR * MAX_PATH),
|
||||
("cAlternateFileName", WCHAR * 14)]
|
||||
]
|
||||
"""
|
||||
|
||||
@property
|
||||
def file_attributes(self):
|
||||
return self.dwFileAttributes
|
||||
|
||||
@property
|
||||
def creation_time(self):
|
||||
return self.ftCreationTime
|
||||
|
||||
@property
|
||||
def last_access_time(self):
|
||||
return self.ftLastAccessTime
|
||||
|
||||
@property
|
||||
def last_write_time(self):
|
||||
return self.ftLastWriteTime
|
||||
|
||||
@property
|
||||
def file_size_words(self):
|
||||
return [self.nFileSizeHigh, self.nFileSizeLow]
|
||||
|
||||
@property
|
||||
def reserved(self):
|
||||
return [self.dwReserved0, self.dwReserved1]
|
||||
|
||||
@property
|
||||
def filename(self):
|
||||
return self.cFileName
|
||||
|
||||
@property
|
||||
def alternate_filename(self):
|
||||
return self.cAlternateFileName
|
||||
|
||||
@property
|
||||
def file_size(self):
|
||||
return self.nFileSizeHigh << 32 + self.nFileSizeLow
|
||||
|
||||
|
||||
LPWIN32_FIND_DATA = ctypes.POINTER(ctypes.wintypes.WIN32_FIND_DATAW)
|
||||
|
||||
FindFirstFile = ctypes.windll.kernel32.FindFirstFileW
|
||||
FindFirstFile.argtypes = (ctypes.wintypes.LPWSTR, LPWIN32_FIND_DATA)
|
||||
FindFirstFile.restype = ctypes.wintypes.HANDLE
|
||||
FindNextFile = ctypes.windll.kernel32.FindNextFileW
|
||||
FindNextFile.argtypes = (ctypes.wintypes.HANDLE, LPWIN32_FIND_DATA)
|
||||
FindNextFile.restype = ctypes.wintypes.BOOLEAN
|
||||
|
||||
ctypes.windll.kernel32.FindClose.argtypes = ctypes.wintypes.HANDLE,
|
||||
|
||||
SCS_32BIT_BINARY = 0 # A 32-bit Windows-based application
|
||||
SCS_64BIT_BINARY = 6 # A 64-bit Windows-based application
|
||||
SCS_DOS_BINARY = 1 # An MS-DOS-based application
|
||||
SCS_OS216_BINARY = 5 # A 16-bit OS/2-based application
|
||||
SCS_PIF_BINARY = 3 # A PIF file that executes an MS-DOS-based application
|
||||
SCS_POSIX_BINARY = 4 # A POSIX-based application
|
||||
SCS_WOW_BINARY = 2 # A 16-bit Windows-based application
|
||||
|
||||
_GetBinaryType = ctypes.windll.kernel32.GetBinaryTypeW
|
||||
_GetBinaryType.argtypes = (
|
||||
ctypes.wintypes.LPWSTR, ctypes.POINTER(ctypes.wintypes.DWORD),
|
||||
)
|
||||
_GetBinaryType.restype = ctypes.wintypes.BOOL
|
||||
|
||||
FILEOP_FLAGS = ctypes.wintypes.WORD
|
||||
|
||||
|
||||
class BY_HANDLE_FILE_INFORMATION(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('file_attributes', ctypes.wintypes.DWORD),
|
||||
('creation_time', ctypes.wintypes.FILETIME),
|
||||
('last_access_time', ctypes.wintypes.FILETIME),
|
||||
('last_write_time', ctypes.wintypes.FILETIME),
|
||||
('volume_serial_number', ctypes.wintypes.DWORD),
|
||||
('file_size_high', ctypes.wintypes.DWORD),
|
||||
('file_size_low', ctypes.wintypes.DWORD),
|
||||
('number_of_links', ctypes.wintypes.DWORD),
|
||||
('file_index_high', ctypes.wintypes.DWORD),
|
||||
('file_index_low', ctypes.wintypes.DWORD),
|
||||
]
|
||||
|
||||
@property
|
||||
def file_size(self):
|
||||
return (self.file_size_high << 32) + self.file_size_low
|
||||
|
||||
@property
|
||||
def file_index(self):
|
||||
return (self.file_index_high << 32) + self.file_index_low
|
||||
|
||||
|
||||
GetFileInformationByHandle = ctypes.windll.kernel32.GetFileInformationByHandle
|
||||
GetFileInformationByHandle.restype = ctypes.wintypes.BOOL
|
||||
GetFileInformationByHandle.argtypes = (
|
||||
ctypes.wintypes.HANDLE,
|
||||
ctypes.POINTER(BY_HANDLE_FILE_INFORMATION),
|
||||
)
|
||||
|
||||
|
||||
class SHFILEOPSTRUCT(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('status_dialog', ctypes.wintypes.HWND),
|
||||
('operation', ctypes.wintypes.UINT),
|
||||
('from_', ctypes.wintypes.LPWSTR),
|
||||
('to', ctypes.wintypes.LPWSTR),
|
||||
('flags', FILEOP_FLAGS),
|
||||
('operations_aborted', ctypes.wintypes.BOOL),
|
||||
('name_mapping_handles', ctypes.wintypes.LPVOID),
|
||||
('progress_title', ctypes.wintypes.LPWSTR),
|
||||
]
|
||||
|
||||
|
||||
_SHFileOperation = ctypes.windll.shell32.SHFileOperationW
|
||||
_SHFileOperation.argtypes = [ctypes.POINTER(SHFILEOPSTRUCT)]
|
||||
_SHFileOperation.restype = ctypes.c_int
|
||||
|
||||
FOF_ALLOWUNDO = 64
|
||||
FOF_NOCONFIRMATION = 16
|
||||
FO_DELETE = 3
|
||||
|
||||
ReplaceFile = ctypes.windll.kernel32.ReplaceFileW
|
||||
ReplaceFile.restype = ctypes.wintypes.BOOL
|
||||
ReplaceFile.argtypes = [
|
||||
ctypes.wintypes.LPWSTR,
|
||||
ctypes.wintypes.LPWSTR,
|
||||
ctypes.wintypes.LPWSTR,
|
||||
ctypes.wintypes.DWORD,
|
||||
ctypes.wintypes.LPVOID,
|
||||
ctypes.wintypes.LPVOID,
|
||||
]
|
||||
|
||||
REPLACEFILE_WRITE_THROUGH = 0x1
|
||||
REPLACEFILE_IGNORE_MERGE_ERRORS = 0x2
|
||||
REPLACEFILE_IGNORE_ACL_ERRORS = 0x4
|
||||
|
||||
|
||||
class STAT_STRUCT(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('dev', ctypes.c_uint),
|
||||
('ino', ctypes.c_ushort),
|
||||
('mode', ctypes.c_ushort),
|
||||
('nlink', ctypes.c_short),
|
||||
('uid', ctypes.c_short),
|
||||
('gid', ctypes.c_short),
|
||||
('rdev', ctypes.c_uint),
|
||||
# the following 4 fields are ctypes.c_uint64 for _stat64
|
||||
('size', ctypes.c_uint),
|
||||
('atime', ctypes.c_uint),
|
||||
('mtime', ctypes.c_uint),
|
||||
('ctime', ctypes.c_uint),
|
||||
]
|
||||
|
||||
|
||||
_wstat = ctypes.windll.msvcrt._wstat
|
||||
_wstat.argtypes = [ctypes.wintypes.LPWSTR, ctypes.POINTER(STAT_STRUCT)]
|
||||
_wstat.restype = ctypes.c_int
|
||||
|
||||
FILE_NOTIFY_CHANGE_LAST_WRITE = 0x10
|
||||
|
||||
FindFirstChangeNotification = (
|
||||
ctypes.windll.kernel32.FindFirstChangeNotificationW)
|
||||
FindFirstChangeNotification.argtypes = (
|
||||
ctypes.wintypes.LPWSTR, ctypes.wintypes.BOOL, ctypes.wintypes.DWORD,
|
||||
)
|
||||
FindFirstChangeNotification.restype = ctypes.wintypes.HANDLE
|
||||
|
||||
FindCloseChangeNotification = (
|
||||
ctypes.windll.kernel32.FindCloseChangeNotification)
|
||||
FindCloseChangeNotification.argtypes = ctypes.wintypes.HANDLE,
|
||||
FindCloseChangeNotification.restype = ctypes.wintypes.BOOL
|
||||
|
||||
FindNextChangeNotification = ctypes.windll.kernel32.FindNextChangeNotification
|
||||
FindNextChangeNotification.argtypes = ctypes.wintypes.HANDLE,
|
||||
FindNextChangeNotification.restype = ctypes.wintypes.BOOL
|
||||
|
||||
FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000
|
||||
IO_REPARSE_TAG_SYMLINK = 0xA000000C
|
||||
FSCTL_GET_REPARSE_POINT = 0x900a8
|
||||
|
||||
LPDWORD = ctypes.POINTER(ctypes.wintypes.DWORD)
|
||||
LPOVERLAPPED = ctypes.wintypes.LPVOID
|
||||
|
||||
DeviceIoControl = ctypes.windll.kernel32.DeviceIoControl
|
||||
DeviceIoControl.argtypes = [
|
||||
ctypes.wintypes.HANDLE,
|
||||
ctypes.wintypes.DWORD,
|
||||
ctypes.wintypes.LPVOID,
|
||||
ctypes.wintypes.DWORD,
|
||||
ctypes.wintypes.LPVOID,
|
||||
ctypes.wintypes.DWORD,
|
||||
LPDWORD,
|
||||
LPOVERLAPPED,
|
||||
]
|
||||
DeviceIoControl.restype = ctypes.wintypes.BOOL
|
||||
|
||||
|
||||
class REPARSE_DATA_BUFFER(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('tag', ctypes.c_ulong),
|
||||
('data_length', ctypes.c_ushort),
|
||||
('reserved', ctypes.c_ushort),
|
||||
('substitute_name_offset', ctypes.c_ushort),
|
||||
('substitute_name_length', ctypes.c_ushort),
|
||||
('print_name_offset', ctypes.c_ushort),
|
||||
('print_name_length', ctypes.c_ushort),
|
||||
('flags', ctypes.c_ulong),
|
||||
('path_buffer', ctypes.c_byte * 1),
|
||||
]
|
||||
|
||||
def get_print_name(self):
|
||||
wchar_size = ctypes.sizeof(ctypes.wintypes.WCHAR)
|
||||
arr_typ = ctypes.wintypes.WCHAR * (self.print_name_length // wchar_size)
|
||||
data = ctypes.byref(self.path_buffer, self.print_name_offset)
|
||||
return ctypes.cast(data, ctypes.POINTER(arr_typ)).contents.value
|
||||
|
||||
def get_substitute_name(self):
|
||||
wchar_size = ctypes.sizeof(ctypes.wintypes.WCHAR)
|
||||
arr_typ = ctypes.wintypes.WCHAR * (self.substitute_name_length // wchar_size)
|
||||
data = ctypes.byref(self.path_buffer, self.substitute_name_offset)
|
||||
return ctypes.cast(data, ctypes.POINTER(arr_typ)).contents.value
|
217
libs/win/jaraco/windows/api/inet.py
Normal file
217
libs/win/jaraco/windows/api/inet.py
Normal file
|
@ -0,0 +1,217 @@
|
|||
import struct
|
||||
import ctypes.wintypes
|
||||
from ctypes.wintypes import DWORD, WCHAR, BYTE, BOOL
|
||||
|
||||
|
||||
# from mprapi.h
|
||||
MAX_INTERFACE_NAME_LEN = 2**8
|
||||
|
||||
# from iprtrmib.h
|
||||
MAXLEN_PHYSADDR = 2**3
|
||||
MAXLEN_IFDESCR = 2**8
|
||||
|
||||
# from iptypes.h
|
||||
MAX_ADAPTER_ADDRESS_LENGTH = 8
|
||||
MAX_DHCPV6_DUID_LENGTH = 130
|
||||
|
||||
|
||||
class MIB_IFROW(ctypes.Structure):
|
||||
_fields_ = (
|
||||
('name', WCHAR * MAX_INTERFACE_NAME_LEN),
|
||||
('index', DWORD),
|
||||
('type', DWORD),
|
||||
('MTU', DWORD),
|
||||
('speed', DWORD),
|
||||
('physical_address_length', DWORD),
|
||||
('physical_address_raw', BYTE * MAXLEN_PHYSADDR),
|
||||
('admin_status', DWORD),
|
||||
('operational_status', DWORD),
|
||||
('last_change', DWORD),
|
||||
('octets_received', DWORD),
|
||||
('unicast_packets_received', DWORD),
|
||||
('non_unicast_packets_received', DWORD),
|
||||
('incoming_discards', DWORD),
|
||||
('incoming_errors', DWORD),
|
||||
('incoming_unknown_protocols', DWORD),
|
||||
('octets_sent', DWORD),
|
||||
('unicast_packets_sent', DWORD),
|
||||
('non_unicast_packets_sent', DWORD),
|
||||
('outgoing_discards', DWORD),
|
||||
('outgoing_errors', DWORD),
|
||||
('outgoing_queue_length', DWORD),
|
||||
('description_length', DWORD),
|
||||
('description_raw', ctypes.c_char * MAXLEN_IFDESCR),
|
||||
)
|
||||
|
||||
def _get_binary_property(self, name):
|
||||
val_prop = '{0}_raw'.format(name)
|
||||
val = getattr(self, val_prop)
|
||||
len_prop = '{0}_length'.format(name)
|
||||
length = getattr(self, len_prop)
|
||||
return str(memoryview(val))[:length]
|
||||
|
||||
@property
|
||||
def physical_address(self):
|
||||
return self._get_binary_property('physical_address')
|
||||
|
||||
@property
|
||||
def description(self):
|
||||
return self._get_binary_property('description')
|
||||
|
||||
|
||||
class MIB_IFTABLE(ctypes.Structure):
|
||||
_fields_ = (
|
||||
('num_entries', DWORD), # dwNumEntries
|
||||
('entries', MIB_IFROW * 0), # table
|
||||
)
|
||||
|
||||
|
||||
class MIB_IPADDRROW(ctypes.Structure):
|
||||
_fields_ = (
|
||||
('address_num', DWORD),
|
||||
('index', DWORD),
|
||||
('mask', DWORD),
|
||||
('broadcast_address', DWORD),
|
||||
('reassembly_size', DWORD),
|
||||
('unused', ctypes.c_ushort),
|
||||
('type', ctypes.c_ushort),
|
||||
)
|
||||
|
||||
@property
|
||||
def address(self):
|
||||
"The address in big-endian"
|
||||
_ = struct.pack('L', self.address_num)
|
||||
return struct.unpack('!L', _)[0]
|
||||
|
||||
|
||||
class MIB_IPADDRTABLE(ctypes.Structure):
|
||||
_fields_ = (
|
||||
('num_entries', DWORD),
|
||||
('entries', MIB_IPADDRROW * 0),
|
||||
)
|
||||
|
||||
|
||||
class SOCKADDR(ctypes.Structure):
|
||||
_fields_ = (
|
||||
('family', ctypes.c_ushort),
|
||||
('data', ctypes.c_byte * 14),
|
||||
)
|
||||
|
||||
|
||||
LPSOCKADDR = ctypes.POINTER(SOCKADDR)
|
||||
|
||||
|
||||
class SOCKET_ADDRESS(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('address', LPSOCKADDR),
|
||||
('length', ctypes.c_int),
|
||||
]
|
||||
|
||||
|
||||
class _IP_ADAPTER_ADDRESSES_METRIC(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('length', ctypes.c_ulong),
|
||||
('interface_index', DWORD),
|
||||
]
|
||||
|
||||
|
||||
class _IP_ADAPTER_ADDRESSES_U1(ctypes.Union):
|
||||
_fields_ = [
|
||||
('alignment', ctypes.c_ulonglong),
|
||||
('metric', _IP_ADAPTER_ADDRESSES_METRIC),
|
||||
]
|
||||
|
||||
|
||||
class IP_ADAPTER_ADDRESSES(ctypes.Structure):
|
||||
pass
|
||||
|
||||
|
||||
LP_IP_ADAPTER_ADDRESSES = ctypes.POINTER(IP_ADAPTER_ADDRESSES)
|
||||
|
||||
# for now, just use void * for pointers to unused structures
|
||||
PIP_ADAPTER_UNICAST_ADDRESS = ctypes.c_void_p
|
||||
PIP_ADAPTER_ANYCAST_ADDRESS = ctypes.c_void_p
|
||||
PIP_ADAPTER_MULTICAST_ADDRESS = ctypes.c_void_p
|
||||
PIP_ADAPTER_DNS_SERVER_ADDRESS = ctypes.c_void_p
|
||||
PIP_ADAPTER_PREFIX = ctypes.c_void_p
|
||||
PIP_ADAPTER_WINS_SERVER_ADDRESS_LH = ctypes.c_void_p
|
||||
PIP_ADAPTER_GATEWAY_ADDRESS_LH = ctypes.c_void_p
|
||||
PIP_ADAPTER_DNS_SUFFIX = ctypes.c_void_p
|
||||
|
||||
IF_OPER_STATUS = ctypes.c_uint # this is an enum, consider
|
||||
# http://code.activestate.com/recipes/576415/
|
||||
IF_LUID = ctypes.c_uint64
|
||||
|
||||
NET_IF_COMPARTMENT_ID = ctypes.c_uint32
|
||||
GUID = ctypes.c_byte * 16
|
||||
NET_IF_NETWORK_GUID = GUID
|
||||
NET_IF_CONNECTION_TYPE = ctypes.c_uint # enum
|
||||
TUNNEL_TYPE = ctypes.c_uint # enum
|
||||
|
||||
IP_ADAPTER_ADDRESSES._fields_ = [
|
||||
# ('u', _IP_ADAPTER_ADDRESSES_U1),
|
||||
('length', ctypes.c_ulong),
|
||||
('interface_index', DWORD),
|
||||
('next', LP_IP_ADAPTER_ADDRESSES),
|
||||
('adapter_name', ctypes.c_char_p),
|
||||
('first_unicast_address', PIP_ADAPTER_UNICAST_ADDRESS),
|
||||
('first_anycast_address', PIP_ADAPTER_ANYCAST_ADDRESS),
|
||||
('first_multicast_address', PIP_ADAPTER_MULTICAST_ADDRESS),
|
||||
('first_dns_server_address', PIP_ADAPTER_DNS_SERVER_ADDRESS),
|
||||
('dns_suffix', ctypes.c_wchar_p),
|
||||
('description', ctypes.c_wchar_p),
|
||||
('friendly_name', ctypes.c_wchar_p),
|
||||
('byte', BYTE * MAX_ADAPTER_ADDRESS_LENGTH),
|
||||
('physical_address_length', DWORD),
|
||||
('flags', DWORD),
|
||||
('mtu', DWORD),
|
||||
('interface_type', DWORD),
|
||||
('oper_status', IF_OPER_STATUS),
|
||||
('ipv6_interface_index', DWORD),
|
||||
('zone_indices', DWORD),
|
||||
('first_prefix', PIP_ADAPTER_PREFIX),
|
||||
('transmit_link_speed', ctypes.c_uint64),
|
||||
('receive_link_speed', ctypes.c_uint64),
|
||||
('first_wins_server_address', PIP_ADAPTER_WINS_SERVER_ADDRESS_LH),
|
||||
('first_gateway_address', PIP_ADAPTER_GATEWAY_ADDRESS_LH),
|
||||
('ipv4_metric', ctypes.c_ulong),
|
||||
('ipv6_metric', ctypes.c_ulong),
|
||||
('luid', IF_LUID),
|
||||
('dhcpv4_server', SOCKET_ADDRESS),
|
||||
('compartment_id', NET_IF_COMPARTMENT_ID),
|
||||
('network_guid', NET_IF_NETWORK_GUID),
|
||||
('connection_type', NET_IF_CONNECTION_TYPE),
|
||||
('tunnel_type', TUNNEL_TYPE),
|
||||
('dhcpv6_server', SOCKET_ADDRESS),
|
||||
('dhcpv6_client_duid', ctypes.c_byte * MAX_DHCPV6_DUID_LENGTH),
|
||||
('dhcpv6_client_duid_length', ctypes.c_ulong),
|
||||
('dhcpv6_iaid', ctypes.c_ulong),
|
||||
('first_dns_suffix', PIP_ADAPTER_DNS_SUFFIX),
|
||||
]
|
||||
|
||||
# define some parameters to the API Functions
|
||||
GetIfTable = ctypes.windll.iphlpapi.GetIfTable
|
||||
GetIfTable.argtypes = [
|
||||
ctypes.POINTER(MIB_IFTABLE),
|
||||
ctypes.POINTER(ctypes.c_ulong),
|
||||
BOOL,
|
||||
]
|
||||
GetIfTable.restype = DWORD
|
||||
|
||||
GetIpAddrTable = ctypes.windll.iphlpapi.GetIpAddrTable
|
||||
GetIpAddrTable.argtypes = [
|
||||
ctypes.POINTER(MIB_IPADDRTABLE),
|
||||
ctypes.POINTER(ctypes.c_ulong),
|
||||
BOOL,
|
||||
]
|
||||
GetIpAddrTable.restype = DWORD
|
||||
|
||||
GetAdaptersAddresses = ctypes.windll.iphlpapi.GetAdaptersAddresses
|
||||
GetAdaptersAddresses.argtypes = [
|
||||
ctypes.c_ulong,
|
||||
ctypes.c_ulong,
|
||||
ctypes.c_void_p,
|
||||
ctypes.POINTER(IP_ADAPTER_ADDRESSES),
|
||||
ctypes.POINTER(ctypes.c_ulong),
|
||||
]
|
||||
GetAdaptersAddresses.restype = ctypes.c_ulong
|
9
libs/win/jaraco/windows/api/library.py
Normal file
9
libs/win/jaraco/windows/api/library.py
Normal file
|
@ -0,0 +1,9 @@
|
|||
import ctypes.wintypes
|
||||
|
||||
GetModuleFileName = ctypes.windll.kernel32.GetModuleFileNameW
|
||||
GetModuleFileName.argtypes = (
|
||||
ctypes.wintypes.HANDLE,
|
||||
ctypes.wintypes.LPWSTR,
|
||||
ctypes.wintypes.DWORD,
|
||||
)
|
||||
GetModuleFileName.restype = ctypes.wintypes.DWORD
|
45
libs/win/jaraco/windows/api/memory.py
Normal file
45
libs/win/jaraco/windows/api/memory.py
Normal file
|
@ -0,0 +1,45 @@
|
|||
import ctypes.wintypes
|
||||
|
||||
GMEM_MOVEABLE = 0x2
|
||||
|
||||
GlobalAlloc = ctypes.windll.kernel32.GlobalAlloc
|
||||
GlobalAlloc.argtypes = ctypes.wintypes.UINT, ctypes.c_size_t
|
||||
GlobalAlloc.restype = ctypes.wintypes.HANDLE
|
||||
|
||||
GlobalLock = ctypes.windll.kernel32.GlobalLock
|
||||
GlobalLock.argtypes = ctypes.wintypes.HGLOBAL,
|
||||
GlobalLock.restype = ctypes.wintypes.LPVOID
|
||||
|
||||
GlobalUnlock = ctypes.windll.kernel32.GlobalUnlock
|
||||
GlobalUnlock.argtypes = ctypes.wintypes.HGLOBAL,
|
||||
GlobalUnlock.restype = ctypes.wintypes.BOOL
|
||||
|
||||
GlobalSize = ctypes.windll.kernel32.GlobalSize
|
||||
GlobalSize.argtypes = ctypes.wintypes.HGLOBAL,
|
||||
GlobalSize.restype = ctypes.c_size_t
|
||||
|
||||
CreateFileMapping = ctypes.windll.kernel32.CreateFileMappingW
|
||||
CreateFileMapping.argtypes = [
|
||||
ctypes.wintypes.HANDLE,
|
||||
ctypes.c_void_p,
|
||||
ctypes.wintypes.DWORD,
|
||||
ctypes.wintypes.DWORD,
|
||||
ctypes.wintypes.DWORD,
|
||||
ctypes.wintypes.LPWSTR,
|
||||
]
|
||||
CreateFileMapping.restype = ctypes.wintypes.HANDLE
|
||||
|
||||
MapViewOfFile = ctypes.windll.kernel32.MapViewOfFile
|
||||
MapViewOfFile.restype = ctypes.wintypes.HANDLE
|
||||
|
||||
UnmapViewOfFile = ctypes.windll.kernel32.UnmapViewOfFile
|
||||
UnmapViewOfFile.argtypes = ctypes.wintypes.HANDLE,
|
||||
|
||||
RtlMoveMemory = ctypes.windll.kernel32.RtlMoveMemory
|
||||
RtlMoveMemory.argtypes = (
|
||||
ctypes.c_void_p,
|
||||
ctypes.c_void_p,
|
||||
ctypes.c_size_t,
|
||||
)
|
||||
|
||||
ctypes.windll.kernel32.LocalFree.argtypes = ctypes.wintypes.HLOCAL,
|
54
libs/win/jaraco/windows/api/message.py
Normal file
54
libs/win/jaraco/windows/api/message.py
Normal file
|
@ -0,0 +1,54 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
jaraco.windows.message
|
||||
|
||||
Windows Messaging support
|
||||
"""
|
||||
|
||||
import ctypes
|
||||
from ctypes.wintypes import HWND, UINT, WPARAM, LPARAM, DWORD, LPVOID
|
||||
|
||||
import six
|
||||
|
||||
LRESULT = LPARAM
|
||||
|
||||
|
||||
class LPARAM_wstr(LPARAM):
|
||||
"""
|
||||
A special instance of LPARAM that can be constructed from a string
|
||||
instance (for functions such as SendMessage, whose LPARAM may point to
|
||||
a unicode string).
|
||||
"""
|
||||
@classmethod
|
||||
def from_param(cls, param):
|
||||
if isinstance(param, six.string_types):
|
||||
return LPVOID.from_param(six.text_type(param))
|
||||
return LPARAM.from_param(param)
|
||||
|
||||
|
||||
SendMessage = ctypes.windll.user32.SendMessageW
|
||||
SendMessage.argtypes = (HWND, UINT, WPARAM, LPARAM_wstr)
|
||||
SendMessage.restype = LRESULT
|
||||
|
||||
HWND_BROADCAST = 0xFFFF
|
||||
WM_SETTINGCHANGE = 0x1A
|
||||
|
||||
# constants from http://msdn.microsoft.com
|
||||
# /en-us/library/ms644952%28v=vs.85%29.aspx
|
||||
SMTO_ABORTIFHUNG = 0x02
|
||||
SMTO_BLOCK = 0x01
|
||||
SMTO_NORMAL = 0x00
|
||||
SMTO_NOTIMEOUTIFNOTHUNG = 0x08
|
||||
SMTO_ERRORONEXIT = 0x20
|
||||
|
||||
SendMessageTimeout = ctypes.windll.user32.SendMessageTimeoutW
|
||||
SendMessageTimeout.argtypes = SendMessage.argtypes + (
|
||||
UINT, UINT, ctypes.POINTER(DWORD)
|
||||
)
|
||||
SendMessageTimeout.restype = LRESULT
|
||||
|
||||
|
||||
def unicode_as_lparam(source):
|
||||
pointer = ctypes.cast(ctypes.c_wchar_p(source), ctypes.c_void_p)
|
||||
return LPARAM(pointer.value)
|
30
libs/win/jaraco/windows/api/net.py
Normal file
30
libs/win/jaraco/windows/api/net.py
Normal file
|
@ -0,0 +1,30 @@
|
|||
import ctypes.wintypes
|
||||
|
||||
# MPR - Multiple Provider Router
|
||||
mpr = ctypes.windll.mpr
|
||||
|
||||
RESOURCETYPE_ANY = 0
|
||||
|
||||
|
||||
class NETRESOURCE(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('scope', ctypes.wintypes.DWORD),
|
||||
('type', ctypes.wintypes.DWORD),
|
||||
('display_type', ctypes.wintypes.DWORD),
|
||||
('usage', ctypes.wintypes.DWORD),
|
||||
('local_name', ctypes.wintypes.LPWSTR),
|
||||
('remote_name', ctypes.wintypes.LPWSTR),
|
||||
('comment', ctypes.wintypes.LPWSTR),
|
||||
('provider', ctypes.wintypes.LPWSTR),
|
||||
]
|
||||
|
||||
|
||||
LPNETRESOURCE = ctypes.POINTER(NETRESOURCE)
|
||||
|
||||
WNetAddConnection2 = mpr.WNetAddConnection2W
|
||||
WNetAddConnection2.argtypes = (
|
||||
LPNETRESOURCE,
|
||||
ctypes.wintypes.LPCWSTR,
|
||||
ctypes.wintypes.LPCWSTR,
|
||||
ctypes.wintypes.DWORD,
|
||||
)
|
37
libs/win/jaraco/windows/api/power.py
Normal file
37
libs/win/jaraco/windows/api/power.py
Normal file
|
@ -0,0 +1,37 @@
|
|||
import ctypes.wintypes
|
||||
|
||||
|
||||
class SYSTEM_POWER_STATUS(ctypes.Structure):
|
||||
_fields_ = (
|
||||
('ac_line_status', ctypes.wintypes.BYTE),
|
||||
('battery_flag', ctypes.wintypes.BYTE),
|
||||
('battery_life_percent', ctypes.wintypes.BYTE),
|
||||
('reserved', ctypes.wintypes.BYTE),
|
||||
('battery_life_time', ctypes.wintypes.DWORD),
|
||||
('battery_full_life_time', ctypes.wintypes.DWORD),
|
||||
)
|
||||
|
||||
@property
|
||||
def ac_line_status_string(self):
|
||||
return {
|
||||
0: 'offline', 1: 'online', 255: 'unknown'}[self.ac_line_status]
|
||||
|
||||
|
||||
LPSYSTEM_POWER_STATUS = ctypes.POINTER(SYSTEM_POWER_STATUS)
|
||||
GetSystemPowerStatus = ctypes.windll.kernel32.GetSystemPowerStatus
|
||||
GetSystemPowerStatus.argtypes = LPSYSTEM_POWER_STATUS,
|
||||
GetSystemPowerStatus.restype = ctypes.wintypes.BOOL
|
||||
|
||||
SetThreadExecutionState = ctypes.windll.kernel32.SetThreadExecutionState
|
||||
SetThreadExecutionState.argtypes = [ctypes.c_uint]
|
||||
SetThreadExecutionState.restype = ctypes.c_uint
|
||||
|
||||
|
||||
class ES:
|
||||
"""
|
||||
Execution state constants
|
||||
"""
|
||||
continuous = 0x80000000
|
||||
system_required = 1
|
||||
display_required = 2
|
||||
awaymode_required = 0x40
|
117
libs/win/jaraco/windows/api/privilege.py
Normal file
117
libs/win/jaraco/windows/api/privilege.py
Normal file
|
@ -0,0 +1,117 @@
|
|||
import ctypes.wintypes
|
||||
|
||||
|
||||
class LUID(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('low_part', ctypes.wintypes.DWORD),
|
||||
('high_part', ctypes.wintypes.LONG),
|
||||
]
|
||||
|
||||
def __eq__(self, other):
|
||||
return (
|
||||
self.high_part == other.high_part and
|
||||
self.low_part == other.low_part
|
||||
)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not (self == other)
|
||||
|
||||
|
||||
LookupPrivilegeValue = ctypes.windll.advapi32.LookupPrivilegeValueW
|
||||
LookupPrivilegeValue.argtypes = (
|
||||
ctypes.wintypes.LPWSTR, # system name
|
||||
ctypes.wintypes.LPWSTR, # name
|
||||
ctypes.POINTER(LUID),
|
||||
)
|
||||
LookupPrivilegeValue.restype = ctypes.wintypes.BOOL
|
||||
|
||||
|
||||
class TOKEN_INFORMATION_CLASS:
|
||||
TokenUser = 1
|
||||
TokenGroups = 2
|
||||
TokenPrivileges = 3
|
||||
# ... see http://msdn.microsoft.com/en-us/library/aa379626%28VS.85%29.aspx
|
||||
|
||||
|
||||
SE_PRIVILEGE_ENABLED_BY_DEFAULT = 0x00000001
|
||||
SE_PRIVILEGE_ENABLED = 0x00000002
|
||||
SE_PRIVILEGE_REMOVED = 0x00000004
|
||||
SE_PRIVILEGE_USED_FOR_ACCESS = 0x80000000
|
||||
|
||||
|
||||
class LUID_AND_ATTRIBUTES(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('LUID', LUID),
|
||||
('attributes', ctypes.wintypes.DWORD),
|
||||
]
|
||||
|
||||
def is_enabled(self):
|
||||
return bool(self.attributes & SE_PRIVILEGE_ENABLED)
|
||||
|
||||
def enable(self):
|
||||
self.attributes |= SE_PRIVILEGE_ENABLED
|
||||
|
||||
def get_name(self):
|
||||
size = ctypes.wintypes.DWORD(10240)
|
||||
buf = ctypes.create_unicode_buffer(size.value)
|
||||
res = LookupPrivilegeName(None, self.LUID, buf, size)
|
||||
if res == 0:
|
||||
raise RuntimeError
|
||||
return buf[:size.value]
|
||||
|
||||
def __str__(self):
|
||||
res = self.get_name()
|
||||
if self.is_enabled():
|
||||
res += ' (enabled)'
|
||||
return res
|
||||
|
||||
|
||||
LookupPrivilegeName = ctypes.windll.advapi32.LookupPrivilegeNameW
|
||||
LookupPrivilegeName.argtypes = (
|
||||
ctypes.wintypes.LPWSTR, # lpSystemName
|
||||
ctypes.POINTER(LUID), # lpLuid
|
||||
ctypes.wintypes.LPWSTR, # lpName
|
||||
ctypes.POINTER(ctypes.wintypes.DWORD), # cchName
|
||||
)
|
||||
LookupPrivilegeName.restype = ctypes.wintypes.BOOL
|
||||
|
||||
|
||||
class TOKEN_PRIVILEGES(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('count', ctypes.wintypes.DWORD),
|
||||
('privileges', LUID_AND_ATTRIBUTES * 0),
|
||||
]
|
||||
|
||||
def get_array(self):
|
||||
array_type = LUID_AND_ATTRIBUTES * self.count
|
||||
privileges = ctypes.cast(
|
||||
self.privileges, ctypes.POINTER(array_type)).contents
|
||||
return privileges
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.get_array())
|
||||
|
||||
|
||||
PTOKEN_PRIVILEGES = ctypes.POINTER(TOKEN_PRIVILEGES)
|
||||
|
||||
GetTokenInformation = ctypes.windll.advapi32.GetTokenInformation
|
||||
GetTokenInformation.argtypes = [
|
||||
ctypes.wintypes.HANDLE, # TokenHandle
|
||||
ctypes.c_uint, # TOKEN_INFORMATION_CLASS value
|
||||
ctypes.c_void_p, # TokenInformation
|
||||
ctypes.wintypes.DWORD, # TokenInformationLength
|
||||
ctypes.POINTER(ctypes.wintypes.DWORD), # ReturnLength
|
||||
]
|
||||
GetTokenInformation.restype = ctypes.wintypes.BOOL
|
||||
|
||||
# http://msdn.microsoft.com/en-us/library/aa375202%28VS.85%29.aspx
|
||||
AdjustTokenPrivileges = ctypes.windll.advapi32.AdjustTokenPrivileges
|
||||
AdjustTokenPrivileges.restype = ctypes.wintypes.BOOL
|
||||
AdjustTokenPrivileges.argtypes = [
|
||||
ctypes.wintypes.HANDLE, # TokenHandle
|
||||
ctypes.wintypes.BOOL, # DisableAllPrivileges
|
||||
PTOKEN_PRIVILEGES, # NewState (optional)
|
||||
ctypes.wintypes.DWORD, # BufferLength of PreviousState
|
||||
PTOKEN_PRIVILEGES, # PreviousState (out, optional)
|
||||
ctypes.POINTER(ctypes.wintypes.DWORD), # ReturnLength
|
||||
]
|
11
libs/win/jaraco/windows/api/process.py
Normal file
11
libs/win/jaraco/windows/api/process.py
Normal file
|
@ -0,0 +1,11 @@
|
|||
import ctypes.wintypes
|
||||
|
||||
TOKEN_ALL_ACCESS = 0xf01ff
|
||||
|
||||
GetCurrentProcess = ctypes.windll.kernel32.GetCurrentProcess
|
||||
GetCurrentProcess.restype = ctypes.wintypes.HANDLE
|
||||
OpenProcessToken = ctypes.windll.advapi32.OpenProcessToken
|
||||
OpenProcessToken.argtypes = (
|
||||
ctypes.wintypes.HANDLE, ctypes.wintypes.DWORD,
|
||||
ctypes.POINTER(ctypes.wintypes.HANDLE))
|
||||
OpenProcessToken.restype = ctypes.wintypes.BOOL
|
139
libs/win/jaraco/windows/api/security.py
Normal file
139
libs/win/jaraco/windows/api/security.py
Normal file
|
@ -0,0 +1,139 @@
|
|||
import ctypes.wintypes
|
||||
|
||||
# from WinNT.h
|
||||
READ_CONTROL = 0x00020000
|
||||
STANDARD_RIGHTS_REQUIRED = 0x000F0000
|
||||
STANDARD_RIGHTS_READ = READ_CONTROL
|
||||
STANDARD_RIGHTS_WRITE = READ_CONTROL
|
||||
STANDARD_RIGHTS_EXECUTE = READ_CONTROL
|
||||
STANDARD_RIGHTS_ALL = 0x001F0000
|
||||
|
||||
# from NTSecAPI.h
|
||||
POLICY_VIEW_LOCAL_INFORMATION = 0x00000001
|
||||
POLICY_VIEW_AUDIT_INFORMATION = 0x00000002
|
||||
POLICY_GET_PRIVATE_INFORMATION = 0x00000004
|
||||
POLICY_TRUST_ADMIN = 0x00000008
|
||||
POLICY_CREATE_ACCOUNT = 0x00000010
|
||||
POLICY_CREATE_SECRET = 0x00000020
|
||||
POLICY_CREATE_PRIVILEGE = 0x00000040
|
||||
POLICY_SET_DEFAULT_QUOTA_LIMITS = 0x00000080
|
||||
POLICY_SET_AUDIT_REQUIREMENTS = 0x00000100
|
||||
POLICY_AUDIT_LOG_ADMIN = 0x00000200
|
||||
POLICY_SERVER_ADMIN = 0x00000400
|
||||
POLICY_LOOKUP_NAMES = 0x00000800
|
||||
POLICY_NOTIFICATION = 0x00001000
|
||||
|
||||
POLICY_ALL_ACCESS = (
|
||||
STANDARD_RIGHTS_REQUIRED |
|
||||
POLICY_VIEW_LOCAL_INFORMATION |
|
||||
POLICY_VIEW_AUDIT_INFORMATION |
|
||||
POLICY_GET_PRIVATE_INFORMATION |
|
||||
POLICY_TRUST_ADMIN |
|
||||
POLICY_CREATE_ACCOUNT |
|
||||
POLICY_CREATE_SECRET |
|
||||
POLICY_CREATE_PRIVILEGE |
|
||||
POLICY_SET_DEFAULT_QUOTA_LIMITS |
|
||||
POLICY_SET_AUDIT_REQUIREMENTS |
|
||||
POLICY_AUDIT_LOG_ADMIN |
|
||||
POLICY_SERVER_ADMIN |
|
||||
POLICY_LOOKUP_NAMES)
|
||||
|
||||
|
||||
POLICY_READ = (
|
||||
STANDARD_RIGHTS_READ |
|
||||
POLICY_VIEW_AUDIT_INFORMATION |
|
||||
POLICY_GET_PRIVATE_INFORMATION)
|
||||
|
||||
POLICY_WRITE = (
|
||||
STANDARD_RIGHTS_WRITE |
|
||||
POLICY_TRUST_ADMIN |
|
||||
POLICY_CREATE_ACCOUNT |
|
||||
POLICY_CREATE_SECRET |
|
||||
POLICY_CREATE_PRIVILEGE |
|
||||
POLICY_SET_DEFAULT_QUOTA_LIMITS |
|
||||
POLICY_SET_AUDIT_REQUIREMENTS |
|
||||
POLICY_AUDIT_LOG_ADMIN |
|
||||
POLICY_SERVER_ADMIN)
|
||||
|
||||
POLICY_EXECUTE = (
|
||||
STANDARD_RIGHTS_EXECUTE |
|
||||
POLICY_VIEW_LOCAL_INFORMATION |
|
||||
POLICY_LOOKUP_NAMES)
|
||||
|
||||
|
||||
class TokenAccess:
|
||||
TOKEN_QUERY = 0x8
|
||||
|
||||
|
||||
class TokenInformationClass:
|
||||
TokenUser = 1
|
||||
|
||||
|
||||
class TOKEN_USER(ctypes.Structure):
|
||||
num = 1
|
||||
_fields_ = [
|
||||
('SID', ctypes.c_void_p),
|
||||
('ATTRIBUTES', ctypes.wintypes.DWORD),
|
||||
]
|
||||
|
||||
|
||||
class SECURITY_DESCRIPTOR(ctypes.Structure):
|
||||
"""
|
||||
typedef struct _SECURITY_DESCRIPTOR
|
||||
{
|
||||
UCHAR Revision;
|
||||
UCHAR Sbz1;
|
||||
SECURITY_DESCRIPTOR_CONTROL Control;
|
||||
PSID Owner;
|
||||
PSID Group;
|
||||
PACL Sacl;
|
||||
PACL Dacl;
|
||||
} SECURITY_DESCRIPTOR;
|
||||
"""
|
||||
SECURITY_DESCRIPTOR_CONTROL = ctypes.wintypes.USHORT
|
||||
REVISION = 1
|
||||
|
||||
_fields_ = [
|
||||
('Revision', ctypes.c_ubyte),
|
||||
('Sbz1', ctypes.c_ubyte),
|
||||
('Control', SECURITY_DESCRIPTOR_CONTROL),
|
||||
('Owner', ctypes.c_void_p),
|
||||
('Group', ctypes.c_void_p),
|
||||
('Sacl', ctypes.c_void_p),
|
||||
('Dacl', ctypes.c_void_p),
|
||||
]
|
||||
|
||||
|
||||
class SECURITY_ATTRIBUTES(ctypes.Structure):
|
||||
"""
|
||||
typedef struct _SECURITY_ATTRIBUTES {
|
||||
DWORD nLength;
|
||||
LPVOID lpSecurityDescriptor;
|
||||
BOOL bInheritHandle;
|
||||
} SECURITY_ATTRIBUTES;
|
||||
"""
|
||||
_fields_ = [
|
||||
('nLength', ctypes.wintypes.DWORD),
|
||||
('lpSecurityDescriptor', ctypes.c_void_p),
|
||||
('bInheritHandle', ctypes.wintypes.BOOL),
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(SECURITY_ATTRIBUTES, self).__init__(*args, **kwargs)
|
||||
self.nLength = ctypes.sizeof(SECURITY_ATTRIBUTES)
|
||||
|
||||
@property
|
||||
def descriptor(self):
|
||||
return self._descriptor
|
||||
|
||||
@descriptor.setter
|
||||
def descriptor(self, value):
|
||||
self._descriptor = value
|
||||
self.lpSecurityDescriptor = ctypes.addressof(value)
|
||||
|
||||
|
||||
ctypes.windll.advapi32.SetSecurityDescriptorOwner.argtypes = (
|
||||
ctypes.POINTER(SECURITY_DESCRIPTOR),
|
||||
ctypes.c_void_p,
|
||||
ctypes.wintypes.BOOL,
|
||||
)
|
130
libs/win/jaraco/windows/api/shell.py
Normal file
130
libs/win/jaraco/windows/api/shell.py
Normal file
|
@ -0,0 +1,130 @@
|
|||
import ctypes.wintypes
|
||||
BOOL = ctypes.wintypes.BOOL
|
||||
|
||||
|
||||
class SHELLSTATE(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('show_all_objects', BOOL, 1),
|
||||
('show_extensions', BOOL, 1),
|
||||
('no_confirm_recycle', BOOL, 1),
|
||||
('show_sys_files', BOOL, 1),
|
||||
('show_comp_color', BOOL, 1),
|
||||
('double_click_in_web_view', BOOL, 1),
|
||||
('desktop_HTML', BOOL, 1),
|
||||
('win95_classic', BOOL, 1),
|
||||
('dont_pretty_path', BOOL, 1),
|
||||
('show_attrib_col', BOOL, 1),
|
||||
('map_network_drive_button', BOOL, 1),
|
||||
('show_info_tip', BOOL, 1),
|
||||
('hide_icons', BOOL, 1),
|
||||
('web_view', BOOL, 1),
|
||||
('filter', BOOL, 1),
|
||||
('show_super_hidden', BOOL, 1),
|
||||
('no_net_crawling', BOOL, 1),
|
||||
('win95_unused', ctypes.wintypes.DWORD),
|
||||
('param_sort', ctypes.wintypes.LONG),
|
||||
('sort_direction', ctypes.c_int),
|
||||
('version', ctypes.wintypes.UINT),
|
||||
('not_used', ctypes.wintypes.UINT),
|
||||
('sep_process', BOOL, 1),
|
||||
('start_panel_on', BOOL, 1),
|
||||
('show_start_page', BOOL, 1),
|
||||
('auto_check_select', BOOL, 1),
|
||||
('icons_only', BOOL, 1),
|
||||
('show_type_overlay', BOOL, 1),
|
||||
('spare_flags', ctypes.wintypes.UINT, 13),
|
||||
]
|
||||
|
||||
|
||||
SSF_SHOWALLOBJECTS = 0x00000001
|
||||
"The fShowAllObjects member is being requested."
|
||||
|
||||
SSF_SHOWEXTENSIONS = 0x00000002
|
||||
"The fShowExtensions member is being requested."
|
||||
|
||||
SSF_HIDDENFILEEXTS = 0x00000004
|
||||
"Not used."
|
||||
|
||||
SSF_SERVERADMINUI = 0x00000004
|
||||
"Not used."
|
||||
|
||||
SSF_SHOWCOMPCOLOR = 0x00000008
|
||||
"The fShowCompColor member is being requested."
|
||||
|
||||
SSF_SORTCOLUMNS = 0x00000010
|
||||
"The lParamSort and iSortDirection members are being requested."
|
||||
|
||||
SSF_SHOWSYSFILES = 0x00000020
|
||||
"The fShowSysFiles member is being requested."
|
||||
|
||||
SSF_DOUBLECLICKINWEBVIEW = 0x00000080
|
||||
"The fDoubleClickInWebView member is being requested."
|
||||
|
||||
SSF_SHOWATTRIBCOL = 0x00000100
|
||||
"The fShowAttribCol member is being requested. (Windows Vista: Not used.)"
|
||||
|
||||
SSF_DESKTOPHTML = 0x00000200
|
||||
"""
|
||||
The fDesktopHTML member is being requested. Set is not available.
|
||||
Instead, for versions of Microsoft Windows prior to Windows XP,
|
||||
enable Desktop HTML by IActiveDesktop. The use of IActiveDesktop
|
||||
for this purpose, however, is not recommended for Windows XP and
|
||||
later versions of Windows, and is deprecated in Windows Vista.
|
||||
"""
|
||||
|
||||
SSF_WIN95CLASSIC = 0x00000400
|
||||
"The fWin95Classic member is being requested."
|
||||
|
||||
SSF_DONTPRETTYPATH = 0x00000800
|
||||
"The fDontPrettyPath member is being requested."
|
||||
|
||||
SSF_MAPNETDRVBUTTON = 0x00001000
|
||||
"The fMapNetDrvBtn member is being requested."
|
||||
|
||||
SSF_SHOWINFOTIP = 0x00002000
|
||||
"The fShowInfoTip member is being requested."
|
||||
|
||||
SSF_HIDEICONS = 0x00004000
|
||||
"The fHideIcons member is being requested."
|
||||
|
||||
SSF_NOCONFIRMRECYCLE = 0x00008000
|
||||
"The fNoConfirmRecycle member is being requested."
|
||||
|
||||
SSF_FILTER = 0x00010000
|
||||
"The fFilter member is being requested. (Windows Vista: Not used.)"
|
||||
|
||||
SSF_WEBVIEW = 0x00020000
|
||||
"The fWebView member is being requested."
|
||||
|
||||
SSF_SHOWSUPERHIDDEN = 0x00040000
|
||||
"The fShowSuperHidden member is being requested."
|
||||
|
||||
SSF_SEPPROCESS = 0x00080000
|
||||
"The fSepProcess member is being requested."
|
||||
|
||||
SSF_NONETCRAWLING = 0x00100000
|
||||
"Windows XP and later. The fNoNetCrawling member is being requested."
|
||||
|
||||
SSF_STARTPANELON = 0x00200000
|
||||
"Windows XP and later. The fStartPanelOn member is being requested."
|
||||
|
||||
SSF_SHOWSTARTPAGE = 0x00400000
|
||||
"Not used."
|
||||
|
||||
SSF_AUTOCHECKSELECT = 0x00800000
|
||||
"Windows Vista and later. The fAutoCheckSelect member is being requested."
|
||||
|
||||
SSF_ICONSONLY = 0x01000000
|
||||
"Windows Vista and later. The fIconsOnly member is being requested."
|
||||
|
||||
SSF_SHOWTYPEOVERLAY = 0x02000000
|
||||
"Windows Vista and later. The fShowTypeOverlay member is being requested."
|
||||
|
||||
|
||||
SHGetSetSettings = ctypes.windll.shell32.SHGetSetSettings
|
||||
SHGetSetSettings.argtypes = [
|
||||
ctypes.POINTER(SHELLSTATE),
|
||||
ctypes.wintypes.DWORD,
|
||||
ctypes.wintypes.BOOL, # get or set (True: set)
|
||||
]
|
||||
SHGetSetSettings.restype = None
|
14
libs/win/jaraco/windows/api/system.py
Normal file
14
libs/win/jaraco/windows/api/system.py
Normal file
|
@ -0,0 +1,14 @@
|
|||
import ctypes.wintypes
|
||||
|
||||
SystemParametersInfo = ctypes.windll.user32.SystemParametersInfoW
|
||||
SystemParametersInfo.argtypes = (
|
||||
ctypes.wintypes.UINT,
|
||||
ctypes.wintypes.UINT,
|
||||
ctypes.c_void_p,
|
||||
ctypes.wintypes.UINT,
|
||||
)
|
||||
|
||||
SPI_GETACTIVEWINDOWTRACKING = 0x1000
|
||||
SPI_SETACTIVEWINDOWTRACKING = 0x1001
|
||||
SPI_GETACTIVEWNDTRKTIMEOUT = 0x2002
|
||||
SPI_SETACTIVEWNDTRKTIMEOUT = 0x2003
|
10
libs/win/jaraco/windows/api/user.py
Normal file
10
libs/win/jaraco/windows/api/user.py
Normal file
|
@ -0,0 +1,10 @@
|
|||
import ctypes.wintypes
|
||||
|
||||
try:
|
||||
from ctypes.wintypes import LPDWORD
|
||||
except ImportError:
|
||||
LPDWORD = ctypes.POINTER(ctypes.wintypes.DWORD)
|
||||
|
||||
GetUserName = ctypes.windll.advapi32.GetUserNameW
|
||||
GetUserName.argtypes = ctypes.wintypes.LPWSTR, LPDWORD
|
||||
GetUserName.restype = ctypes.wintypes.DWORD
|
226
libs/win/jaraco/windows/clipboard.py
Normal file
226
libs/win/jaraco/windows/clipboard.py
Normal file
|
@ -0,0 +1,226 @@
|
|||
from __future__ import with_statement, print_function
|
||||
|
||||
import sys
|
||||
import re
|
||||
import itertools
|
||||
from contextlib import contextmanager
|
||||
import io
|
||||
import ctypes
|
||||
from ctypes import windll
|
||||
|
||||
import six
|
||||
from six.moves import map
|
||||
|
||||
from jaraco.windows.api import clipboard, memory
|
||||
from jaraco.windows.error import handle_nonzero_success, WindowsError
|
||||
from jaraco.windows.memory import LockedMemory
|
||||
|
||||
__all__ = (
|
||||
'GetClipboardData', 'CloseClipboard',
|
||||
'SetClipboardData', 'OpenClipboard',
|
||||
)
|
||||
|
||||
|
||||
def OpenClipboard(owner=None):
|
||||
"""
|
||||
Open the clipboard.
|
||||
|
||||
owner
|
||||
[in] Handle to the window to be associated with the open clipboard.
|
||||
If this parameter is None, the open clipboard is associated with the
|
||||
current task.
|
||||
"""
|
||||
handle_nonzero_success(windll.user32.OpenClipboard(owner))
|
||||
|
||||
|
||||
def CloseClipboard():
|
||||
handle_nonzero_success(windll.user32.CloseClipboard())
|
||||
|
||||
|
||||
data_handlers = dict()
|
||||
|
||||
|
||||
def handles(*formats):
|
||||
def register(func):
|
||||
for format in formats:
|
||||
data_handlers[format] = func
|
||||
return func
|
||||
return register
|
||||
|
||||
|
||||
def nts(buffer):
|
||||
"""
|
||||
Null Terminated String
|
||||
Get the portion of bytestring buffer up to a null character.
|
||||
"""
|
||||
result, null, rest = buffer.partition('\x00')
|
||||
return result
|
||||
|
||||
|
||||
@handles(clipboard.CF_DIBV5, clipboard.CF_DIB)
|
||||
def raw_data(handle):
|
||||
return LockedMemory(handle).data
|
||||
|
||||
|
||||
@handles(clipboard.CF_TEXT)
|
||||
def text_string(handle):
|
||||
return nts(raw_data(handle))
|
||||
|
||||
|
||||
@handles(clipboard.CF_UNICODETEXT)
|
||||
def unicode_string(handle):
|
||||
return nts(raw_data(handle).decode('utf-16'))
|
||||
|
||||
|
||||
@handles(clipboard.CF_BITMAP)
|
||||
def as_bitmap(handle):
|
||||
# handle is HBITMAP
|
||||
raise NotImplementedError("Can't convert to DIB")
|
||||
# todo: use GetDIBits http://msdn.microsoft.com
|
||||
# /en-us/library/dd144879%28v=VS.85%29.aspx
|
||||
|
||||
|
||||
@handles(clipboard.CF_HTML)
|
||||
class HTMLSnippet(object):
|
||||
def __init__(self, handle):
|
||||
self.data = nts(raw_data(handle).decode('utf-8'))
|
||||
self.headers = self.parse_headers(self.data)
|
||||
|
||||
@property
|
||||
def html(self):
|
||||
return self.data[self.headers['StartHTML']:]
|
||||
|
||||
@staticmethod
|
||||
def parse_headers(data):
|
||||
d = io.StringIO(data)
|
||||
|
||||
def header_line(line):
|
||||
return re.match('(\w+):(.*)', line)
|
||||
headers = map(header_line, d)
|
||||
# grab headers until they no longer match
|
||||
headers = itertools.takewhile(bool, headers)
|
||||
|
||||
def best_type(value):
|
||||
try:
|
||||
return int(value)
|
||||
except ValueError:
|
||||
pass
|
||||
try:
|
||||
return float(value)
|
||||
except ValueError:
|
||||
pass
|
||||
return value
|
||||
pairs = (
|
||||
(header.group(1), best_type(header.group(2)))
|
||||
for header
|
||||
in headers
|
||||
)
|
||||
return dict(pairs)
|
||||
|
||||
|
||||
def GetClipboardData(type=clipboard.CF_UNICODETEXT):
|
||||
if type not in data_handlers:
|
||||
raise NotImplementedError("No support for data of type %d" % type)
|
||||
handle = clipboard.GetClipboardData(type)
|
||||
if handle is None:
|
||||
raise TypeError("No clipboard data of type %d" % type)
|
||||
return data_handlers[type](handle)
|
||||
|
||||
|
||||
def EmptyClipboard():
|
||||
handle_nonzero_success(windll.user32.EmptyClipboard())
|
||||
|
||||
|
||||
def SetClipboardData(type, content):
|
||||
"""
|
||||
Modeled after http://msdn.microsoft.com
|
||||
/en-us/library/ms649016%28VS.85%29.aspx
|
||||
#_win32_Copying_Information_to_the_Clipboard
|
||||
"""
|
||||
allocators = {
|
||||
clipboard.CF_TEXT: ctypes.create_string_buffer,
|
||||
clipboard.CF_UNICODETEXT: ctypes.create_unicode_buffer,
|
||||
clipboard.CF_HTML: ctypes.create_string_buffer,
|
||||
}
|
||||
if type not in allocators:
|
||||
raise NotImplementedError(
|
||||
"Only text and HTML types are supported at this time")
|
||||
# allocate the memory for the data
|
||||
content = allocators[type](content)
|
||||
flags = memory.GMEM_MOVEABLE
|
||||
size = ctypes.sizeof(content)
|
||||
handle_to_copy = windll.kernel32.GlobalAlloc(flags, size)
|
||||
with LockedMemory(handle_to_copy) as lm:
|
||||
ctypes.memmove(lm.data_ptr, content, size)
|
||||
result = clipboard.SetClipboardData(type, handle_to_copy)
|
||||
if result is None:
|
||||
raise WindowsError()
|
||||
|
||||
|
||||
def set_text(source):
|
||||
with context():
|
||||
EmptyClipboard()
|
||||
SetClipboardData(clipboard.CF_TEXT, source)
|
||||
|
||||
|
||||
def get_text():
|
||||
with context():
|
||||
result = GetClipboardData(clipboard.CF_TEXT)
|
||||
return result
|
||||
|
||||
|
||||
def set_unicode_text(source):
|
||||
with context():
|
||||
EmptyClipboard()
|
||||
SetClipboardData(clipboard.CF_UNICODETEXT, source)
|
||||
|
||||
|
||||
def get_unicode_text():
|
||||
with context():
|
||||
return GetClipboardData()
|
||||
|
||||
|
||||
def get_html():
|
||||
with context():
|
||||
result = GetClipboardData(clipboard.CF_HTML)
|
||||
return result
|
||||
|
||||
|
||||
def set_html(source):
|
||||
with context():
|
||||
EmptyClipboard()
|
||||
SetClipboardData(clipboard.CF_UNICODETEXT, source)
|
||||
|
||||
|
||||
def get_image():
|
||||
with context():
|
||||
return GetClipboardData(clipboard.CF_DIB)
|
||||
|
||||
|
||||
def paste_stdout():
|
||||
getter = get_unicode_text if six.PY3 else get_text
|
||||
sys.stdout.write(getter())
|
||||
|
||||
|
||||
def stdin_copy():
|
||||
setter = set_unicode_text if six.PY3 else set_text
|
||||
setter(sys.stdin.read())
|
||||
|
||||
|
||||
@contextmanager
|
||||
def context():
|
||||
OpenClipboard()
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
CloseClipboard()
|
||||
|
||||
|
||||
def get_formats():
|
||||
with context():
|
||||
format_index = 0
|
||||
while True:
|
||||
format_index = clipboard.EnumClipboardFormats(format_index)
|
||||
if format_index == 0:
|
||||
break
|
||||
yield format_index
|
22
libs/win/jaraco/windows/cred.py
Normal file
22
libs/win/jaraco/windows/cred.py
Normal file
|
@ -0,0 +1,22 @@
|
|||
import ctypes
|
||||
|
||||
import jaraco.windows.api.credential as api
|
||||
from . import error
|
||||
|
||||
CRED_TYPE_GENERIC = 1
|
||||
|
||||
|
||||
def CredDelete(TargetName, Type, Flags=0):
|
||||
error.handle_nonzero_success(api.CredDelete(TargetName, Type, Flags))
|
||||
|
||||
|
||||
def CredRead(TargetName, Type, Flags=0):
|
||||
cred_pointer = api.PCREDENTIAL()
|
||||
res = api.CredRead(TargetName, Type, Flags, ctypes.byref(cred_pointer))
|
||||
error.handle_nonzero_success(res)
|
||||
return cred_pointer.contents
|
||||
|
||||
|
||||
def CredWrite(Credential, Flags=0):
|
||||
res = api.CredWrite(Credential, Flags)
|
||||
error.handle_nonzero_success(res)
|
155
libs/win/jaraco/windows/dpapi.py
Normal file
155
libs/win/jaraco/windows/dpapi.py
Normal file
|
@ -0,0 +1,155 @@
|
|||
|
||||
"""
|
||||
Python routines to interface with the Microsoft
|
||||
Data Protection API (DPAPI).
|
||||
|
||||
>>> orig_data = b'Ipsum Lorem...'
|
||||
>>> ciphertext = CryptProtectData(orig_data)
|
||||
>>> descr, data = CryptUnprotectData(ciphertext)
|
||||
>>> data == orig_data
|
||||
True
|
||||
"""
|
||||
|
||||
import ctypes
|
||||
from ctypes import wintypes
|
||||
from jaraco.windows.error import handle_nonzero_success
|
||||
|
||||
|
||||
# for type declarations
|
||||
__import__('jaraco.windows.api.memory')
|
||||
|
||||
|
||||
class DATA_BLOB(ctypes.Structure):
|
||||
r"""
|
||||
A data blob structure for use with MS DPAPI functions.
|
||||
|
||||
Initialize with string of characters
|
||||
>>> input = b'abc123\x00456'
|
||||
>>> blob = DATA_BLOB(input)
|
||||
>>> len(blob)
|
||||
10
|
||||
>>> blob.get_data() == input
|
||||
True
|
||||
"""
|
||||
_fields_ = [
|
||||
('data_size', wintypes.DWORD),
|
||||
('data', ctypes.c_void_p),
|
||||
]
|
||||
|
||||
def __init__(self, data=None):
|
||||
super(DATA_BLOB, self).__init__()
|
||||
self.set_data(data)
|
||||
|
||||
def set_data(self, data):
|
||||
"Use this method to set the data for this blob"
|
||||
if data is None:
|
||||
self.data_size = 0
|
||||
self.data = None
|
||||
return
|
||||
self.data_size = len(data)
|
||||
# create a string buffer so that null bytes aren't interpreted
|
||||
# as the end of the string
|
||||
self.data = ctypes.cast(ctypes.create_string_buffer(data), ctypes.c_void_p)
|
||||
|
||||
def get_data(self):
|
||||
"Get the data for this blob"
|
||||
array = ctypes.POINTER(ctypes.c_char * len(self))
|
||||
return ctypes.cast(self.data, array).contents.raw
|
||||
|
||||
def __len__(self):
|
||||
return self.data_size
|
||||
|
||||
def __str__(self):
|
||||
return self.get_data()
|
||||
|
||||
def free(self):
|
||||
"""
|
||||
"data out" blobs have locally-allocated memory.
|
||||
Call this method to free the memory allocated by CryptProtectData
|
||||
and CryptUnprotectData.
|
||||
"""
|
||||
ctypes.windll.kernel32.LocalFree(self.data)
|
||||
|
||||
|
||||
p_DATA_BLOB = ctypes.POINTER(DATA_BLOB)
|
||||
|
||||
_CryptProtectData = ctypes.windll.crypt32.CryptProtectData
|
||||
_CryptProtectData.argtypes = [
|
||||
p_DATA_BLOB, # data in
|
||||
wintypes.LPCWSTR, # data description
|
||||
p_DATA_BLOB, # optional entropy
|
||||
ctypes.c_void_p, # reserved
|
||||
ctypes.c_void_p, # POINTER(CRYPTPROTECT_PROMPTSTRUCT), # prompt struct
|
||||
wintypes.DWORD, # flags
|
||||
p_DATA_BLOB, # data out
|
||||
]
|
||||
_CryptProtectData.restype = wintypes.BOOL
|
||||
|
||||
_CryptUnprotectData = ctypes.windll.crypt32.CryptUnprotectData
|
||||
_CryptUnprotectData.argtypes = [
|
||||
p_DATA_BLOB, # data in
|
||||
ctypes.POINTER(wintypes.LPWSTR), # data description
|
||||
p_DATA_BLOB, # optional entropy
|
||||
ctypes.c_void_p, # reserved
|
||||
ctypes.c_void_p, # POINTER(CRYPTPROTECT_PROMPTSTRUCT), # prompt struct
|
||||
wintypes.DWORD, # flags
|
||||
p_DATA_BLOB, # data out
|
||||
]
|
||||
_CryptUnprotectData.restype = wintypes.BOOL
|
||||
|
||||
CRYPTPROTECT_UI_FORBIDDEN = 0x01
|
||||
|
||||
|
||||
def CryptProtectData(
|
||||
data, description=None, optional_entropy=None,
|
||||
prompt_struct=None, flags=0,
|
||||
):
|
||||
"""
|
||||
Encrypt data
|
||||
"""
|
||||
data_in = DATA_BLOB(data)
|
||||
entropy = DATA_BLOB(optional_entropy) if optional_entropy else None
|
||||
data_out = DATA_BLOB()
|
||||
|
||||
res = _CryptProtectData(
|
||||
data_in,
|
||||
description,
|
||||
entropy,
|
||||
None, # reserved
|
||||
prompt_struct,
|
||||
flags,
|
||||
data_out,
|
||||
)
|
||||
handle_nonzero_success(res)
|
||||
res = data_out.get_data()
|
||||
data_out.free()
|
||||
return res
|
||||
|
||||
|
||||
def CryptUnprotectData(
|
||||
data, optional_entropy=None, prompt_struct=None, flags=0):
|
||||
"""
|
||||
Returns a tuple of (description, data) where description is the
|
||||
the description that was passed to the CryptProtectData call and
|
||||
data is the decrypted result.
|
||||
"""
|
||||
data_in = DATA_BLOB(data)
|
||||
entropy = DATA_BLOB(optional_entropy) if optional_entropy else None
|
||||
data_out = DATA_BLOB()
|
||||
ptr_description = wintypes.LPWSTR()
|
||||
res = _CryptUnprotectData(
|
||||
data_in,
|
||||
ctypes.byref(ptr_description),
|
||||
entropy,
|
||||
None, # reserved
|
||||
prompt_struct,
|
||||
flags | CRYPTPROTECT_UI_FORBIDDEN,
|
||||
data_out,
|
||||
)
|
||||
handle_nonzero_success(res)
|
||||
description = ptr_description.value
|
||||
if ptr_description.value is not None:
|
||||
ctypes.windll.kernel32.LocalFree(ptr_description)
|
||||
res = data_out.get_data()
|
||||
data_out.free()
|
||||
return description, res
|
257
libs/win/jaraco/windows/environ.py
Normal file
257
libs/win/jaraco/windows/environ.py
Normal file
|
@ -0,0 +1,257 @@
|
|||
#!/usr/bin/env python
|
||||
from __future__ import absolute_import
|
||||
|
||||
import sys
|
||||
|
||||
import ctypes
|
||||
import ctypes.wintypes
|
||||
|
||||
import six
|
||||
from six.moves import winreg
|
||||
|
||||
from jaraco.ui.editor import EditableFile
|
||||
|
||||
from jaraco.windows import error
|
||||
from jaraco.windows.api import message, environ
|
||||
from .registry import key_values as registry_key_values
|
||||
|
||||
|
||||
def SetEnvironmentVariable(name, value):
|
||||
error.handle_nonzero_success(environ.SetEnvironmentVariable(name, value))
|
||||
|
||||
|
||||
def ClearEnvironmentVariable(name):
|
||||
error.handle_nonzero_success(environ.SetEnvironmentVariable(name, None))
|
||||
|
||||
|
||||
def GetEnvironmentVariable(name):
|
||||
max_size = 2**15 - 1
|
||||
buffer = ctypes.create_unicode_buffer(max_size)
|
||||
error.handle_nonzero_success(
|
||||
environ.GetEnvironmentVariable(name, buffer, max_size))
|
||||
return buffer.value
|
||||
|
||||
###
|
||||
|
||||
|
||||
class RegisteredEnvironment(object):
|
||||
"""
|
||||
Manages the environment variables as set in the Windows Registry.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def show(class_):
|
||||
for name, value, type in registry_key_values(class_.key):
|
||||
sys.stdout.write('='.join((name, value)) + '\n')
|
||||
|
||||
NoDefault = type('NoDefault', (object,), dict())
|
||||
|
||||
@classmethod
|
||||
def get(class_, name, default=NoDefault):
|
||||
try:
|
||||
value, type = winreg.QueryValueEx(class_.key, name)
|
||||
return value
|
||||
except WindowsError:
|
||||
if default is not class_.NoDefault:
|
||||
return default
|
||||
raise ValueError("No such key", name)
|
||||
|
||||
@classmethod
|
||||
def get_values_list(class_, name, sep):
|
||||
res = class_.get(name.upper(), [])
|
||||
if isinstance(res, six.string_types):
|
||||
res = res.split(sep)
|
||||
return res
|
||||
|
||||
@classmethod
|
||||
def set(class_, name, value, options):
|
||||
# consider opening the key read-only except for here
|
||||
# key = winreg.OpenKey(class_.key, None, 0, winreg.KEY_WRITE)
|
||||
# and follow up by closing it.
|
||||
if not value:
|
||||
return class_.delete(name)
|
||||
do_append = options.append or (
|
||||
name.upper() in ('PATH', 'PATHEXT') and not options.replace
|
||||
)
|
||||
if do_append:
|
||||
sep = ';'
|
||||
values = class_.get_values_list(name, sep) + [value]
|
||||
value = sep.join(values)
|
||||
winreg.SetValueEx(class_.key, name, 0, winreg.REG_EXPAND_SZ, value)
|
||||
class_.notify()
|
||||
|
||||
@classmethod
|
||||
def add(class_, name, value, sep=';'):
|
||||
"""
|
||||
Add a value to a delimited variable, but only when the value isn't
|
||||
already present.
|
||||
"""
|
||||
values = class_.get_values_list(name, sep)
|
||||
if value in values:
|
||||
return
|
||||
new_value = sep.join(values + [value])
|
||||
winreg.SetValueEx(
|
||||
class_.key, name, 0, winreg.REG_EXPAND_SZ, new_value)
|
||||
class_.notify()
|
||||
|
||||
@classmethod
|
||||
def remove_values(class_, name, value_substring, options):
|
||||
sep = ';'
|
||||
values = class_.get_values_list(name, sep)
|
||||
new_values = [
|
||||
value
|
||||
for value in values
|
||||
if value_substring.lower() not in value.lower()
|
||||
]
|
||||
values = sep.join(new_values)
|
||||
winreg.SetValueEx(class_.key, name, 0, winreg.REG_EXPAND_SZ, values)
|
||||
class_.notify()
|
||||
|
||||
@classmethod
|
||||
def edit(class_, name, value='', options=None):
|
||||
# value, options ignored
|
||||
sep = ';'
|
||||
values = class_.get_values_list(name, sep)
|
||||
e = EditableFile('\n'.join(values))
|
||||
e.edit()
|
||||
if e.changed:
|
||||
values = sep.join(e.data.strip().split('\n'))
|
||||
winreg.SetValueEx(class_.key, name, 0, winreg.REG_EXPAND_SZ, values)
|
||||
class_.notify()
|
||||
|
||||
@classmethod
|
||||
def delete(class_, name):
|
||||
winreg.DeleteValue(class_.key, name)
|
||||
class_.notify()
|
||||
|
||||
@classmethod
|
||||
def notify(class_):
|
||||
"""
|
||||
Notify other windows that the environment has changed (following
|
||||
http://support.microsoft.com/kb/104011).
|
||||
"""
|
||||
# TODO: Implement Microsoft UIPI (User Interface Privilege Isolation) to
|
||||
# elevate privilege to system level so the system gets this notification
|
||||
# for now, this must be run as admin to work as expected
|
||||
return_val = ctypes.wintypes.DWORD()
|
||||
res = message.SendMessageTimeout(
|
||||
message.HWND_BROADCAST,
|
||||
message.WM_SETTINGCHANGE,
|
||||
0, # wparam must be null
|
||||
'Environment',
|
||||
message.SMTO_ABORTIFHUNG,
|
||||
5000, # timeout in ms
|
||||
return_val,
|
||||
)
|
||||
error.handle_nonzero_success(res)
|
||||
|
||||
|
||||
class MachineRegisteredEnvironment(RegisteredEnvironment):
|
||||
path = r'SYSTEM\CurrentControlSet\Control\Session Manager\Environment'
|
||||
hklm = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
|
||||
try:
|
||||
key = winreg.OpenKey(
|
||||
hklm, path, 0,
|
||||
winreg.KEY_READ | winreg.KEY_WRITE)
|
||||
except WindowsError:
|
||||
key = winreg.OpenKey(hklm, path, 0, winreg.KEY_READ)
|
||||
|
||||
|
||||
class UserRegisteredEnvironment(RegisteredEnvironment):
|
||||
hkcu = winreg.ConnectRegistry(None, winreg.HKEY_CURRENT_USER)
|
||||
key = winreg.OpenKey(
|
||||
hkcu, 'Environment', 0,
|
||||
winreg.KEY_READ | winreg.KEY_WRITE)
|
||||
|
||||
|
||||
def trim(s):
|
||||
from textwrap import dedent
|
||||
return dedent(s).strip()
|
||||
|
||||
|
||||
def enver(*args):
|
||||
"""
|
||||
%prog [<name>=[value]]
|
||||
|
||||
To show all environment variables, call with no parameters:
|
||||
%prog
|
||||
To Add/Modify/Delete environment variable:
|
||||
%prog <name>=[value]
|
||||
|
||||
If <name> is PATH or PATHEXT, %prog will by default append the value using
|
||||
a semicolon as a separator. Use -r to disable this behavior or -a to force
|
||||
it for variables other than PATH and PATHEXT.
|
||||
|
||||
If append is prescribed, but the value doesn't exist, the value will be
|
||||
created.
|
||||
|
||||
If there is no value, %prog will delete the <name> environment variable.
|
||||
i.e. "PATH="
|
||||
|
||||
To remove a specific value or values from a semicolon-separated
|
||||
multi-value variable (such as PATH), use --remove-value.
|
||||
|
||||
e.g. enver --remove-value PATH=C:\\Unwanted\\Dir\\In\\Path
|
||||
|
||||
Remove-value matches case-insensitive and also matches any substring
|
||||
so the following would also be sufficient to remove the aforementioned
|
||||
undesirable dir.
|
||||
|
||||
enver --remove-value PATH=UNWANTED
|
||||
|
||||
Note that %prog does not affect the current running environment, and can
|
||||
only affect subsequently spawned applications.
|
||||
"""
|
||||
from optparse import OptionParser
|
||||
parser = OptionParser(usage=trim(enver.__doc__))
|
||||
parser.add_option(
|
||||
'-U', '--user-environment',
|
||||
action='store_const', const=UserRegisteredEnvironment,
|
||||
default=MachineRegisteredEnvironment,
|
||||
dest='class_',
|
||||
help="Use the current user's environment",
|
||||
)
|
||||
parser.add_option(
|
||||
'-a', '--append',
|
||||
action='store_true', default=False,
|
||||
help="Append the value to any existing value (default for PATH and PATHEXT)",
|
||||
)
|
||||
parser.add_option(
|
||||
'-r', '--replace',
|
||||
action='store_true', default=False,
|
||||
help="Replace any existing value (used to override default append "
|
||||
"for PATH and PATHEXT)",
|
||||
)
|
||||
parser.add_option(
|
||||
'--remove-value', action='store_true', default=False,
|
||||
help="Remove any matching values from a semicolon-separated "
|
||||
"multi-value variable",
|
||||
)
|
||||
parser.add_option(
|
||||
'-e', '--edit', action='store_true', default=False,
|
||||
help="Edit the value in a local editor",
|
||||
)
|
||||
options, args = parser.parse_args(*args)
|
||||
|
||||
try:
|
||||
param = args.pop()
|
||||
if args:
|
||||
parser.error("Too many parameters specified")
|
||||
raise SystemExit(1)
|
||||
if '=' not in param and not options.edit:
|
||||
parser.error("Expected <name>= or <name>=<value>")
|
||||
raise SystemExit(2)
|
||||
name, sep, value = param.partition('=')
|
||||
method_name = 'set'
|
||||
if options.remove_value:
|
||||
method_name = 'remove_values'
|
||||
if options.edit:
|
||||
method_name = 'edit'
|
||||
method = getattr(options.class_, method_name)
|
||||
method(name, value, options)
|
||||
except IndexError:
|
||||
options.class_.show()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
enver()
|
84
libs/win/jaraco/windows/error.py
Normal file
84
libs/win/jaraco/windows/error.py
Normal file
|
@ -0,0 +1,84 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import sys
|
||||
|
||||
import ctypes
|
||||
import ctypes.wintypes
|
||||
|
||||
import six
|
||||
builtins = six.moves.builtins
|
||||
|
||||
|
||||
__import__('jaraco.windows.api.memory')
|
||||
|
||||
|
||||
def format_system_message(errno):
|
||||
"""
|
||||
Call FormatMessage with a system error number to retrieve
|
||||
the descriptive error message.
|
||||
"""
|
||||
# first some flags used by FormatMessageW
|
||||
ALLOCATE_BUFFER = 0x100
|
||||
FROM_SYSTEM = 0x1000
|
||||
|
||||
# Let FormatMessageW allocate the buffer (we'll free it below)
|
||||
# Also, let it know we want a system error message.
|
||||
flags = ALLOCATE_BUFFER | FROM_SYSTEM
|
||||
source = None
|
||||
message_id = errno
|
||||
language_id = 0
|
||||
result_buffer = ctypes.wintypes.LPWSTR()
|
||||
buffer_size = 0
|
||||
arguments = None
|
||||
bytes = ctypes.windll.kernel32.FormatMessageW(
|
||||
flags,
|
||||
source,
|
||||
message_id,
|
||||
language_id,
|
||||
ctypes.byref(result_buffer),
|
||||
buffer_size,
|
||||
arguments,
|
||||
)
|
||||
# note the following will cause an infinite loop if GetLastError
|
||||
# repeatedly returns an error that cannot be formatted, although
|
||||
# this should not happen.
|
||||
handle_nonzero_success(bytes)
|
||||
message = result_buffer.value
|
||||
ctypes.windll.kernel32.LocalFree(result_buffer)
|
||||
return message
|
||||
|
||||
|
||||
class WindowsError(builtins.WindowsError):
|
||||
"""
|
||||
More info about errors at
|
||||
http://msdn.microsoft.com/en-us/library/ms681381(VS.85).aspx
|
||||
"""
|
||||
|
||||
def __init__(self, value=None):
|
||||
if value is None:
|
||||
value = ctypes.windll.kernel32.GetLastError()
|
||||
strerror = format_system_message(value)
|
||||
if sys.version_info > (3, 3):
|
||||
args = 0, strerror, None, value
|
||||
else:
|
||||
args = value, strerror
|
||||
super(WindowsError, self).__init__(*args)
|
||||
|
||||
@property
|
||||
def message(self):
|
||||
return self.strerror
|
||||
|
||||
@property
|
||||
def code(self):
|
||||
return self.winerror
|
||||
|
||||
def __str__(self):
|
||||
return self.message
|
||||
|
||||
def __repr__(self):
|
||||
return '{self.__class__.__name__}({self.winerror})'.format(**vars())
|
||||
|
||||
|
||||
def handle_nonzero_success(result):
|
||||
if result == 0:
|
||||
raise WindowsError()
|
52
libs/win/jaraco/windows/eventlog.py
Normal file
52
libs/win/jaraco/windows/eventlog.py
Normal file
|
@ -0,0 +1,52 @@
|
|||
import functools
|
||||
|
||||
from six.moves import map
|
||||
|
||||
import win32api
|
||||
import win32evtlog
|
||||
import win32evtlogutil
|
||||
|
||||
error = win32api.error # The error the evtlog module raises.
|
||||
|
||||
|
||||
class EventLog(object):
|
||||
def __init__(self, name="Application", machine_name=None):
|
||||
self.machine_name = machine_name
|
||||
self.name = name
|
||||
self.formatter = functools.partial(
|
||||
win32evtlogutil.FormatMessage, logType=self.name)
|
||||
|
||||
def __enter__(self):
|
||||
if hasattr(self, 'handle'):
|
||||
raise ValueError("Overlapping attempts to use this log context")
|
||||
self.handle = win32evtlog.OpenEventLog(self.machine_name, self.name)
|
||||
return self
|
||||
|
||||
def __exit__(self, *args):
|
||||
win32evtlog.CloseEventLog(self.handle)
|
||||
del self.handle
|
||||
|
||||
_default_flags = (
|
||||
win32evtlog.EVENTLOG_BACKWARDS_READ
|
||||
| win32evtlog.EVENTLOG_SEQUENTIAL_READ
|
||||
)
|
||||
|
||||
def get_records(self, flags=_default_flags):
|
||||
with self:
|
||||
while True:
|
||||
objects = win32evtlog.ReadEventLog(self.handle, flags, 0)
|
||||
if not objects:
|
||||
break
|
||||
for item in objects:
|
||||
yield item
|
||||
|
||||
def __iter__(self):
|
||||
return self.get_records()
|
||||
|
||||
def format_record(self, record):
|
||||
return self.formatter(record)
|
||||
|
||||
def format_records(self, records=None):
|
||||
if records is None:
|
||||
records = self.get_records()
|
||||
return map(self.format_record, records)
|
501
libs/win/jaraco/windows/filesystem/__init__.py
Normal file
501
libs/win/jaraco/windows/filesystem/__init__.py
Normal file
|
@ -0,0 +1,501 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
import sys
|
||||
import operator
|
||||
import collections
|
||||
import functools
|
||||
import stat
|
||||
from ctypes import (
|
||||
POINTER, byref, cast, create_unicode_buffer,
|
||||
create_string_buffer, windll)
|
||||
from ctypes.wintypes import LPWSTR
|
||||
import nt
|
||||
import posixpath
|
||||
|
||||
import six
|
||||
from six.moves import builtins, filter, map
|
||||
|
||||
from jaraco.structures import binary
|
||||
|
||||
from jaraco.windows.error import WindowsError, handle_nonzero_success
|
||||
import jaraco.windows.api.filesystem as api
|
||||
from jaraco.windows import reparse
|
||||
|
||||
|
||||
def mklink():
|
||||
"""
|
||||
Like cmd.exe's mklink except it will infer directory status of the
|
||||
target.
|
||||
"""
|
||||
from optparse import OptionParser
|
||||
parser = OptionParser(usage="usage: %prog [options] link target")
|
||||
parser.add_option(
|
||||
'-d', '--directory',
|
||||
help="Target is a directory (only necessary if not present)",
|
||||
action="store_true")
|
||||
options, args = parser.parse_args()
|
||||
try:
|
||||
link, target = args
|
||||
except ValueError:
|
||||
parser.error("incorrect number of arguments")
|
||||
symlink(target, link, options.directory)
|
||||
sys.stdout.write("Symbolic link created: %(link)s --> %(target)s\n" % vars())
|
||||
|
||||
|
||||
def _is_target_a_directory(link, rel_target):
|
||||
"""
|
||||
If creating a symlink from link to a target, determine if target
|
||||
is a directory (relative to dirname(link)).
|
||||
"""
|
||||
target = os.path.join(os.path.dirname(link), rel_target)
|
||||
return os.path.isdir(target)
|
||||
|
||||
|
||||
def symlink(target, link, target_is_directory=False):
|
||||
"""
|
||||
An implementation of os.symlink for Windows (Vista and greater)
|
||||
"""
|
||||
target_is_directory = (
|
||||
target_is_directory or
|
||||
_is_target_a_directory(link, target)
|
||||
)
|
||||
# normalize the target (MS symlinks don't respect forward slashes)
|
||||
target = os.path.normpath(target)
|
||||
handle_nonzero_success(
|
||||
api.CreateSymbolicLink(link, target, target_is_directory))
|
||||
|
||||
|
||||
def link(target, link):
|
||||
"""
|
||||
Establishes a hard link between an existing file and a new file.
|
||||
"""
|
||||
handle_nonzero_success(api.CreateHardLink(link, target, None))
|
||||
|
||||
|
||||
def is_reparse_point(path):
|
||||
"""
|
||||
Determine if the given path is a reparse point.
|
||||
Return False if the file does not exist or the file attributes cannot
|
||||
be determined.
|
||||
"""
|
||||
res = api.GetFileAttributes(path)
|
||||
return (
|
||||
res != api.INVALID_FILE_ATTRIBUTES
|
||||
and bool(res & api.FILE_ATTRIBUTE_REPARSE_POINT)
|
||||
)
|
||||
|
||||
|
||||
def islink(path):
|
||||
"Determine if the given path is a symlink"
|
||||
return is_reparse_point(path) and is_symlink(path)
|
||||
|
||||
|
||||
def _patch_path(path):
|
||||
"""
|
||||
Paths have a max length of api.MAX_PATH characters (260). If a target path
|
||||
is longer than that, it needs to be made absolute and prepended with
|
||||
\\?\ in order to work with API calls.
|
||||
See http://msdn.microsoft.com/en-us/library/aa365247%28v=vs.85%29.aspx for
|
||||
details.
|
||||
"""
|
||||
if path.startswith('\\\\?\\'):
|
||||
return path
|
||||
abs_path = os.path.abspath(path)
|
||||
if not abs_path[1] == ':':
|
||||
# python doesn't include the drive letter, but \\?\ requires it
|
||||
abs_path = os.getcwd()[:2] + abs_path
|
||||
return '\\\\?\\' + abs_path
|
||||
|
||||
|
||||
def is_symlink(path):
|
||||
"""
|
||||
Assuming path is a reparse point, determine if it's a symlink.
|
||||
"""
|
||||
path = _patch_path(path)
|
||||
try:
|
||||
return _is_symlink(next(find_files(path)))
|
||||
except WindowsError as orig_error:
|
||||
tmpl = "Error accessing {path}: {orig_error.message}"
|
||||
raise builtins.WindowsError(tmpl.format(**locals()))
|
||||
|
||||
|
||||
def _is_symlink(find_data):
|
||||
return find_data.reserved[0] == api.IO_REPARSE_TAG_SYMLINK
|
||||
|
||||
|
||||
def find_files(spec):
|
||||
"""
|
||||
A pythonic wrapper around the FindFirstFile/FindNextFile win32 api.
|
||||
|
||||
>>> root_files = tuple(find_files(r'c:\*'))
|
||||
>>> len(root_files) > 1
|
||||
True
|
||||
>>> root_files[0].filename == root_files[1].filename
|
||||
False
|
||||
|
||||
This test might fail on a non-standard installation
|
||||
>>> 'Windows' in (fd.filename for fd in root_files)
|
||||
True
|
||||
"""
|
||||
fd = api.WIN32_FIND_DATA()
|
||||
handle = api.FindFirstFile(spec, byref(fd))
|
||||
while True:
|
||||
if handle == api.INVALID_HANDLE_VALUE:
|
||||
raise WindowsError()
|
||||
yield fd
|
||||
fd = api.WIN32_FIND_DATA()
|
||||
res = api.FindNextFile(handle, byref(fd))
|
||||
if res == 0: # error
|
||||
error = WindowsError()
|
||||
if error.code == api.ERROR_NO_MORE_FILES:
|
||||
break
|
||||
else:
|
||||
raise error
|
||||
# todo: how to close handle when generator is destroyed?
|
||||
# hint: catch GeneratorExit
|
||||
windll.kernel32.FindClose(handle)
|
||||
|
||||
|
||||
def get_final_path(path):
|
||||
"""
|
||||
For a given path, determine the ultimate location of that path.
|
||||
Useful for resolving symlink targets.
|
||||
This functions wraps the GetFinalPathNameByHandle from the Windows
|
||||
SDK.
|
||||
|
||||
Note, this function fails if a handle cannot be obtained (such as
|
||||
for C:\Pagefile.sys on a stock windows system). Consider using
|
||||
trace_symlink_target instead.
|
||||
"""
|
||||
desired_access = api.NULL
|
||||
share_mode = (
|
||||
api.FILE_SHARE_READ | api.FILE_SHARE_WRITE | api.FILE_SHARE_DELETE
|
||||
)
|
||||
security_attributes = api.LPSECURITY_ATTRIBUTES() # NULL pointer
|
||||
hFile = api.CreateFile(
|
||||
path,
|
||||
desired_access,
|
||||
share_mode,
|
||||
security_attributes,
|
||||
api.OPEN_EXISTING,
|
||||
api.FILE_FLAG_BACKUP_SEMANTICS,
|
||||
api.NULL,
|
||||
)
|
||||
|
||||
if hFile == api.INVALID_HANDLE_VALUE:
|
||||
raise WindowsError()
|
||||
|
||||
buf_size = api.GetFinalPathNameByHandle(
|
||||
hFile, LPWSTR(), 0, api.VOLUME_NAME_DOS)
|
||||
handle_nonzero_success(buf_size)
|
||||
buf = create_unicode_buffer(buf_size)
|
||||
result_length = api.GetFinalPathNameByHandle(
|
||||
hFile, buf, len(buf), api.VOLUME_NAME_DOS)
|
||||
|
||||
assert result_length < len(buf)
|
||||
handle_nonzero_success(result_length)
|
||||
handle_nonzero_success(api.CloseHandle(hFile))
|
||||
|
||||
return buf[:result_length]
|
||||
|
||||
|
||||
def compat_stat(path):
|
||||
"""
|
||||
Generate stat as found on Python 3.2 and later.
|
||||
"""
|
||||
stat = os.stat(path)
|
||||
info = get_file_info(path)
|
||||
# rewrite st_ino, st_dev, and st_nlink based on file info
|
||||
return nt.stat_result(
|
||||
(stat.st_mode,) +
|
||||
(info.file_index, info.volume_serial_number, info.number_of_links) +
|
||||
stat[4:]
|
||||
)
|
||||
|
||||
|
||||
def samefile(f1, f2):
|
||||
"""
|
||||
Backport of samefile from Python 3.2 with support for Windows.
|
||||
"""
|
||||
return posixpath.samestat(compat_stat(f1), compat_stat(f2))
|
||||
|
||||
|
||||
def get_file_info(path):
|
||||
# open the file the same way CPython does in posixmodule.c
|
||||
desired_access = api.FILE_READ_ATTRIBUTES
|
||||
share_mode = 0
|
||||
security_attributes = None
|
||||
creation_disposition = api.OPEN_EXISTING
|
||||
flags_and_attributes = (
|
||||
api.FILE_ATTRIBUTE_NORMAL |
|
||||
api.FILE_FLAG_BACKUP_SEMANTICS |
|
||||
api.FILE_FLAG_OPEN_REPARSE_POINT
|
||||
)
|
||||
template_file = None
|
||||
|
||||
handle = api.CreateFile(
|
||||
path,
|
||||
desired_access,
|
||||
share_mode,
|
||||
security_attributes,
|
||||
creation_disposition,
|
||||
flags_and_attributes,
|
||||
template_file,
|
||||
)
|
||||
|
||||
if handle == api.INVALID_HANDLE_VALUE:
|
||||
raise WindowsError()
|
||||
|
||||
info = api.BY_HANDLE_FILE_INFORMATION()
|
||||
res = api.GetFileInformationByHandle(handle, info)
|
||||
handle_nonzero_success(res)
|
||||
handle_nonzero_success(api.CloseHandle(handle))
|
||||
|
||||
return info
|
||||
|
||||
|
||||
def GetBinaryType(filepath):
|
||||
res = api.DWORD()
|
||||
handle_nonzero_success(api._GetBinaryType(filepath, res))
|
||||
return res
|
||||
|
||||
|
||||
def _make_null_terminated_list(obs):
|
||||
obs = _makelist(obs)
|
||||
if obs is None:
|
||||
return
|
||||
return u'\x00'.join(obs) + u'\x00\x00'
|
||||
|
||||
|
||||
def _makelist(ob):
|
||||
if ob is None:
|
||||
return
|
||||
if not isinstance(ob, (list, tuple, set)):
|
||||
return [ob]
|
||||
return ob
|
||||
|
||||
|
||||
def SHFileOperation(operation, from_, to=None, flags=[]):
|
||||
flags = functools.reduce(operator.or_, flags, 0)
|
||||
from_ = _make_null_terminated_list(from_)
|
||||
to = _make_null_terminated_list(to)
|
||||
params = api.SHFILEOPSTRUCT(0, operation, from_, to, flags)
|
||||
res = api._SHFileOperation(params)
|
||||
if res != 0:
|
||||
raise RuntimeError("SHFileOperation returned %d" % res)
|
||||
|
||||
|
||||
def join(*paths):
|
||||
r"""
|
||||
Wrapper around os.path.join that works with Windows drive letters.
|
||||
|
||||
>>> join('d:\\foo', '\\bar')
|
||||
'd:\\bar'
|
||||
"""
|
||||
paths_with_drives = map(os.path.splitdrive, paths)
|
||||
drives, paths = zip(*paths_with_drives)
|
||||
# the drive we care about is the last one in the list
|
||||
drive = next(filter(None, reversed(drives)), '')
|
||||
return os.path.join(drive, os.path.join(*paths))
|
||||
|
||||
|
||||
def resolve_path(target, start=os.path.curdir):
|
||||
r"""
|
||||
Find a path from start to target where target is relative to start.
|
||||
|
||||
>>> tmp = str(getfixture('tmpdir_as_cwd'))
|
||||
|
||||
>>> findpath('d:\\')
|
||||
'd:\\'
|
||||
|
||||
>>> findpath('d:\\', tmp)
|
||||
'd:\\'
|
||||
|
||||
>>> findpath('\\bar', 'd:\\')
|
||||
'd:\\bar'
|
||||
|
||||
>>> findpath('\\bar', 'd:\\foo') # fails with '\\bar'
|
||||
'd:\\bar'
|
||||
|
||||
>>> findpath('bar', 'd:\\foo')
|
||||
'd:\\foo\\bar'
|
||||
|
||||
>>> findpath('\\baz', 'd:\\foo\\bar') # fails with '\\baz'
|
||||
'd:\\baz'
|
||||
|
||||
>>> os.path.abspath(findpath('\\bar')).lower()
|
||||
'c:\\bar'
|
||||
|
||||
>>> os.path.abspath(findpath('bar'))
|
||||
'...\\bar'
|
||||
|
||||
>>> findpath('..', 'd:\\foo\\bar')
|
||||
'd:\\foo'
|
||||
|
||||
The parent of the root directory is the root directory.
|
||||
>>> findpath('..', 'd:\\')
|
||||
'd:\\'
|
||||
"""
|
||||
return os.path.normpath(join(start, target))
|
||||
|
||||
|
||||
findpath = resolve_path
|
||||
|
||||
|
||||
def trace_symlink_target(link):
|
||||
"""
|
||||
Given a file that is known to be a symlink, trace it to its ultimate
|
||||
target.
|
||||
|
||||
Raises TargetNotPresent when the target cannot be determined.
|
||||
Raises ValueError when the specified link is not a symlink.
|
||||
"""
|
||||
|
||||
if not is_symlink(link):
|
||||
raise ValueError("link must point to a symlink on the system")
|
||||
while is_symlink(link):
|
||||
orig = os.path.dirname(link)
|
||||
link = readlink(link)
|
||||
link = resolve_path(link, orig)
|
||||
return link
|
||||
|
||||
|
||||
def readlink(link):
|
||||
"""
|
||||
readlink(link) -> target
|
||||
Return a string representing the path to which the symbolic link points.
|
||||
"""
|
||||
handle = api.CreateFile(
|
||||
link,
|
||||
0,
|
||||
0,
|
||||
None,
|
||||
api.OPEN_EXISTING,
|
||||
api.FILE_FLAG_OPEN_REPARSE_POINT | api.FILE_FLAG_BACKUP_SEMANTICS,
|
||||
None,
|
||||
)
|
||||
|
||||
if handle == api.INVALID_HANDLE_VALUE:
|
||||
raise WindowsError()
|
||||
|
||||
res = reparse.DeviceIoControl(
|
||||
handle, api.FSCTL_GET_REPARSE_POINT, None, 10240)
|
||||
|
||||
bytes = create_string_buffer(res)
|
||||
p_rdb = cast(bytes, POINTER(api.REPARSE_DATA_BUFFER))
|
||||
rdb = p_rdb.contents
|
||||
if not rdb.tag == api.IO_REPARSE_TAG_SYMLINK:
|
||||
raise RuntimeError("Expected IO_REPARSE_TAG_SYMLINK, but got %d" % rdb.tag)
|
||||
|
||||
handle_nonzero_success(api.CloseHandle(handle))
|
||||
return rdb.get_substitute_name()
|
||||
|
||||
|
||||
def patch_os_module():
|
||||
"""
|
||||
jaraco.windows provides the os.symlink and os.readlink functions.
|
||||
Monkey-patch the os module to include them if not present.
|
||||
"""
|
||||
if not hasattr(os, 'symlink'):
|
||||
os.symlink = symlink
|
||||
os.path.islink = islink
|
||||
if not hasattr(os, 'readlink'):
|
||||
os.readlink = readlink
|
||||
|
||||
|
||||
def find_symlinks(root):
|
||||
for dirpath, dirnames, filenames in os.walk(root):
|
||||
for name in dirnames + filenames:
|
||||
pathname = os.path.join(dirpath, name)
|
||||
if is_symlink(pathname):
|
||||
yield pathname
|
||||
# don't traverse symlinks
|
||||
if name in dirnames:
|
||||
dirnames.remove(name)
|
||||
|
||||
|
||||
def find_symlinks_cmd():
|
||||
"""
|
||||
%prog [start-path]
|
||||
Search the specified path (defaults to the current directory) for symlinks,
|
||||
printing the source and target on each line.
|
||||
"""
|
||||
from optparse import OptionParser
|
||||
from textwrap import dedent
|
||||
parser = OptionParser(usage=dedent(find_symlinks_cmd.__doc__).strip())
|
||||
options, args = parser.parse_args()
|
||||
if not args:
|
||||
args = ['.']
|
||||
root = args.pop()
|
||||
if args:
|
||||
parser.error("unexpected argument(s)")
|
||||
try:
|
||||
for symlink in find_symlinks(root):
|
||||
target = readlink(symlink)
|
||||
dir = ['', 'D'][os.path.isdir(symlink)]
|
||||
msg = '{dir:2}{symlink} --> {target}'.format(**locals())
|
||||
print(msg)
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
|
||||
|
||||
@six.add_metaclass(binary.BitMask)
|
||||
class FileAttributes(int):
|
||||
|
||||
# extract the values from the stat module on Python 3.5
|
||||
# and later.
|
||||
locals().update(
|
||||
(name.split('FILE_ATTRIBUTES_')[1].lower(), value)
|
||||
for name, value in vars(stat).items()
|
||||
if name.startswith('FILE_ATTRIBUTES_')
|
||||
)
|
||||
|
||||
# For Python 3.4 and earlier, define the constants here
|
||||
archive = 0x20
|
||||
compressed = 0x800
|
||||
hidden = 0x2
|
||||
device = 0x40
|
||||
directory = 0x10
|
||||
encrypted = 0x4000
|
||||
normal = 0x80
|
||||
not_content_indexed = 0x2000
|
||||
offline = 0x1000
|
||||
read_only = 0x1
|
||||
reparse_point = 0x400
|
||||
sparse_file = 0x200
|
||||
system = 0x4
|
||||
temporary = 0x100
|
||||
virtual = 0x10000
|
||||
|
||||
@classmethod
|
||||
def get(cls, filepath):
|
||||
attrs = api.GetFileAttributes(filepath)
|
||||
if attrs == api.INVALID_FILE_ATTRIBUTES:
|
||||
raise WindowsError()
|
||||
return cls(attrs)
|
||||
|
||||
|
||||
GetFileAttributes = FileAttributes.get
|
||||
|
||||
|
||||
def SetFileAttributes(filepath, *attrs):
|
||||
"""
|
||||
Set file attributes. e.g.:
|
||||
|
||||
SetFileAttributes('C:\\foo', 'hidden')
|
||||
|
||||
Each attr must be either a numeric value, a constant defined in
|
||||
jaraco.windows.filesystem.api, or one of the nice names
|
||||
defined in this function.
|
||||
"""
|
||||
nice_names = collections.defaultdict(
|
||||
lambda key: key,
|
||||
hidden='FILE_ATTRIBUTE_HIDDEN',
|
||||
read_only='FILE_ATTRIBUTE_READONLY',
|
||||
)
|
||||
flags = (getattr(api, nice_names[attr], attr) for attr in attrs)
|
||||
flags = functools.reduce(operator.or_, flags)
|
||||
handle_nonzero_success(api.SetFileAttributes(filepath, flags))
|
109
libs/win/jaraco/windows/filesystem/backports.py
Normal file
109
libs/win/jaraco/windows/filesystem/backports.py
Normal file
|
@ -0,0 +1,109 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
import os.path
|
||||
|
||||
|
||||
# realpath taken from https://bugs.python.org/file38057/issue9949-v4.patch
|
||||
def realpath(path):
|
||||
if isinstance(path, str):
|
||||
prefix = '\\\\?\\'
|
||||
unc_prefix = prefix + 'UNC'
|
||||
new_unc_prefix = '\\'
|
||||
cwd = os.getcwd()
|
||||
else:
|
||||
prefix = b'\\\\?\\'
|
||||
unc_prefix = prefix + b'UNC'
|
||||
new_unc_prefix = b'\\'
|
||||
cwd = os.getcwdb()
|
||||
had_prefix = path.startswith(prefix)
|
||||
path, ok = _resolve_path(cwd, path, {})
|
||||
# The path returned by _getfinalpathname will always start with \\?\ -
|
||||
# strip off that prefix unless it was already provided on the original
|
||||
# path.
|
||||
if not had_prefix:
|
||||
# For UNC paths, the prefix will actually be \\?\UNC - handle that
|
||||
# case as well.
|
||||
if path.startswith(unc_prefix):
|
||||
path = new_unc_prefix + path[len(unc_prefix):]
|
||||
elif path.startswith(prefix):
|
||||
path = path[len(prefix):]
|
||||
return path
|
||||
|
||||
|
||||
def _resolve_path(path, rest, seen):
|
||||
# Windows normalizes the path before resolving symlinks; be sure to
|
||||
# follow the same behavior.
|
||||
rest = os.path.normpath(rest)
|
||||
|
||||
if isinstance(rest, str):
|
||||
sep = '\\'
|
||||
else:
|
||||
sep = b'\\'
|
||||
|
||||
if os.path.isabs(rest):
|
||||
drive, rest = os.path.splitdrive(rest)
|
||||
path = drive + sep
|
||||
rest = rest[1:]
|
||||
|
||||
while rest:
|
||||
name, _, rest = rest.partition(sep)
|
||||
new_path = os.path.join(path, name) if path else name
|
||||
if os.path.exists(new_path):
|
||||
if not rest:
|
||||
# The whole path exists. Resolve it using the OS.
|
||||
path = os.path._getfinalpathname(new_path)
|
||||
else:
|
||||
# The OS can resolve `new_path`; keep traversing the path.
|
||||
path = new_path
|
||||
elif not os.path.lexists(new_path):
|
||||
# `new_path` does not exist on the filesystem at all. Use the
|
||||
# OS to resolve `path`, if it exists, and then append the
|
||||
# remainder.
|
||||
if os.path.exists(path):
|
||||
path = os.path._getfinalpathname(path)
|
||||
rest = os.path.join(name, rest) if rest else name
|
||||
return os.path.join(path, rest), True
|
||||
else:
|
||||
# We have a symbolic link that the OS cannot resolve. Try to
|
||||
# resolve it ourselves.
|
||||
|
||||
# On Windows, symbolic link resolution can be partially or
|
||||
# fully disabled [1]. The end result of a disabled symlink
|
||||
# appears the same as a broken symlink (lexists() returns True
|
||||
# but exists() returns False). And in both cases, the link can
|
||||
# still be read using readlink(). Call stat() and check the
|
||||
# resulting error code to ensure we don't circumvent the
|
||||
# Windows symbolic link restrictions.
|
||||
# [1] https://technet.microsoft.com/en-us/library/cc754077.aspx
|
||||
try:
|
||||
os.stat(new_path)
|
||||
except OSError as e:
|
||||
# WinError 1463: The symbolic link cannot be followed
|
||||
# because its type is disabled.
|
||||
if e.winerror == 1463:
|
||||
raise
|
||||
|
||||
key = os.path.normcase(new_path)
|
||||
if key in seen:
|
||||
# This link has already been seen; try to use the
|
||||
# previously resolved value.
|
||||
path = seen[key]
|
||||
if path is None:
|
||||
# It has not yet been resolved, which means we must
|
||||
# have a symbolic link loop. Return what we have
|
||||
# resolved so far plus the remainder of the path (who
|
||||
# cares about the Zen of Python?).
|
||||
path = os.path.join(new_path, rest) if rest else new_path
|
||||
return path, False
|
||||
else:
|
||||
# Mark this link as in the process of being resolved.
|
||||
seen[key] = None
|
||||
# Try to resolve it.
|
||||
path, ok = _resolve_path(path, os.readlink(new_path), seen)
|
||||
if ok:
|
||||
# Resolution succeded; store the resolved value.
|
||||
seen[key] = path
|
||||
else:
|
||||
# Resolution failed; punt.
|
||||
return (os.path.join(path, rest) if rest else path), False
|
||||
return path, True
|
271
libs/win/jaraco/windows/filesystem/change.py
Normal file
271
libs/win/jaraco/windows/filesystem/change.py
Normal file
|
@ -0,0 +1,271 @@
|
|||
# -*- coding: UTF-8 -*-
|
||||
|
||||
"""
|
||||
FileChange
|
||||
Classes and routines for monitoring the file system for changes.
|
||||
|
||||
Copyright © 2004, 2011, 2013 Jason R. Coombs
|
||||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
import sys
|
||||
import datetime
|
||||
import re
|
||||
from threading import Thread
|
||||
import itertools
|
||||
import logging
|
||||
|
||||
import six
|
||||
|
||||
from more_itertools.recipes import consume
|
||||
import jaraco.text
|
||||
|
||||
import jaraco.windows.api.filesystem as fs
|
||||
from jaraco.windows.api import event
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class NotifierException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class FileFilter(object):
|
||||
def set_root(self, root):
|
||||
self.root = root
|
||||
|
||||
def _get_file_path(self, filename):
|
||||
try:
|
||||
filename = os.path.join(self.root, filename)
|
||||
except AttributeError:
|
||||
pass
|
||||
return filename
|
||||
|
||||
|
||||
class ModifiedTimeFilter(FileFilter):
|
||||
"""
|
||||
Returns true for each call where the modified time of the file is after
|
||||
the cutoff time.
|
||||
"""
|
||||
def __init__(self, cutoff):
|
||||
self.cutoff = cutoff
|
||||
|
||||
def __call__(self, file):
|
||||
filepath = self._get_file_path(file)
|
||||
last_mod = datetime.datetime.utcfromtimestamp(
|
||||
os.stat(filepath).st_mtime)
|
||||
log.debug('{filepath} last modified at {last_mod}.'.format(**vars()))
|
||||
return last_mod > self.cutoff
|
||||
|
||||
|
||||
class PatternFilter(FileFilter):
|
||||
"""
|
||||
Filter that returns True for files that match pattern (a regular
|
||||
expression).
|
||||
"""
|
||||
def __init__(self, pattern):
|
||||
self.pattern = (
|
||||
re.compile(pattern) if isinstance(pattern, six.string_types)
|
||||
else pattern
|
||||
)
|
||||
|
||||
def __call__(self, file):
|
||||
return bool(self.pattern.match(file, re.I))
|
||||
|
||||
|
||||
class GlobFilter(PatternFilter):
|
||||
"""
|
||||
Filter that returns True for files that match the pattern (a glob
|
||||
expression.
|
||||
"""
|
||||
def __init__(self, expression):
|
||||
super(GlobFilter, self).__init__(
|
||||
self.convert_file_pattern(expression))
|
||||
|
||||
@staticmethod
|
||||
def convert_file_pattern(p):
|
||||
r"""
|
||||
converts a filename specification (such as c:\*.*) to an equivelent
|
||||
regular expression
|
||||
>>> GlobFilter.convert_file_pattern('/*')
|
||||
'/.*'
|
||||
"""
|
||||
subs = (('\\', '\\\\'), ('.', '\\.'), ('*', '.*'), ('?', '.'))
|
||||
return jaraco.text.multi_substitution(*subs)(p)
|
||||
|
||||
|
||||
class AggregateFilter(FileFilter):
|
||||
"""
|
||||
This file filter will aggregate the filters passed to it, and when called,
|
||||
will return the results of each filter ANDed together.
|
||||
"""
|
||||
def __init__(self, *filters):
|
||||
self.filters = filters
|
||||
|
||||
def set_root(self, root):
|
||||
consume(f.set_root(root) for f in self.filters)
|
||||
|
||||
def __call__(self, file):
|
||||
return all(fil(file) for fil in self.filters)
|
||||
|
||||
|
||||
class OncePerModFilter(FileFilter):
|
||||
def __init__(self):
|
||||
self.history = list()
|
||||
|
||||
def __call__(self, file):
|
||||
file = os.path.join(self.root, file)
|
||||
key = file, os.stat(file).st_mtime
|
||||
result = key not in self.history
|
||||
self.history.append(key)
|
||||
if len(self.history) > 100:
|
||||
del self.history[-50:]
|
||||
return result
|
||||
|
||||
|
||||
def files_with_path(files, path):
|
||||
return (os.path.join(path, file) for file in files)
|
||||
|
||||
|
||||
def get_file_paths(walk_result):
|
||||
root, dirs, files = walk_result
|
||||
return files_with_path(files, root)
|
||||
|
||||
|
||||
class Notifier(object):
|
||||
def __init__(self, root='.', filters=[]):
|
||||
# assign the root, verify it exists
|
||||
self.root = root
|
||||
if not os.path.isdir(self.root):
|
||||
raise NotifierException(
|
||||
'Root directory "%s" does not exist' % self.root)
|
||||
self.filters = filters
|
||||
|
||||
self.watch_subtree = False
|
||||
self.quit_event = event.CreateEvent(None, 0, 0, None)
|
||||
self.opm_filter = OncePerModFilter()
|
||||
|
||||
def __del__(self):
|
||||
try:
|
||||
fs.FindCloseChangeNotification(self.hChange)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _get_change_handle(self):
|
||||
# set up to monitor the directory tree specified
|
||||
self.hChange = fs.FindFirstChangeNotification(
|
||||
self.root,
|
||||
self.watch_subtree,
|
||||
fs.FILE_NOTIFY_CHANGE_LAST_WRITE,
|
||||
)
|
||||
|
||||
# make sure it worked; if not, bail
|
||||
INVALID_HANDLE_VALUE = fs.INVALID_HANDLE_VALUE
|
||||
if self.hChange == INVALID_HANDLE_VALUE:
|
||||
raise NotifierException(
|
||||
'Could not set up directory change notification')
|
||||
|
||||
@staticmethod
|
||||
def _filtered_walk(path, file_filter):
|
||||
"""
|
||||
static method that calls os.walk, but filters out
|
||||
anything that doesn't match the filter
|
||||
"""
|
||||
for root, dirs, files in os.walk(path):
|
||||
log.debug('looking in %s', root)
|
||||
log.debug('files is %s', files)
|
||||
file_filter.set_root(root)
|
||||
files = filter(file_filter, files)
|
||||
log.debug('filtered files is %s', files)
|
||||
yield (root, dirs, files)
|
||||
|
||||
def quit(self):
|
||||
event.SetEvent(self.quit_event)
|
||||
|
||||
|
||||
class BlockingNotifier(Notifier):
|
||||
|
||||
@staticmethod
|
||||
def wait_results(*args):
|
||||
""" calls WaitForMultipleObjects repeatedly with args """
|
||||
return itertools.starmap(
|
||||
event.WaitForMultipleObjects,
|
||||
itertools.repeat(args))
|
||||
|
||||
def get_changed_files(self):
|
||||
self._get_change_handle()
|
||||
check_time = datetime.datetime.utcnow()
|
||||
# block (sleep) until something changes in the
|
||||
# target directory or a quit is requested.
|
||||
# timeout so we can catch keyboard interrupts or other exceptions
|
||||
events = (self.hChange, self.quit_event)
|
||||
for result in self.wait_results(events, False, 1000):
|
||||
if result == event.WAIT_TIMEOUT:
|
||||
continue
|
||||
index = result - event.WAIT_OBJECT_0
|
||||
if events[index] is self.quit_event:
|
||||
# quit was received; stop yielding results
|
||||
return
|
||||
|
||||
# something has changed.
|
||||
log.debug('Change notification received')
|
||||
fs.FindNextChangeNotification(self.hChange)
|
||||
next_check_time = datetime.datetime.utcnow()
|
||||
log.debug('Looking for all files changed after %s', check_time)
|
||||
for file in self.find_files_after(check_time):
|
||||
yield file
|
||||
check_time = next_check_time
|
||||
|
||||
def find_files_after(self, cutoff):
|
||||
mtf = ModifiedTimeFilter(cutoff)
|
||||
af = AggregateFilter(mtf, self.opm_filter, *self.filters)
|
||||
results = Notifier._filtered_walk(self.root, af)
|
||||
results = itertools.imap(get_file_paths, results)
|
||||
if self.watch_subtree:
|
||||
result = itertools.chain(*results)
|
||||
else:
|
||||
result = next(results)
|
||||
return result
|
||||
|
||||
|
||||
class ThreadedNotifier(BlockingNotifier, Thread):
|
||||
r"""
|
||||
ThreadedNotifier provides a simple interface that calls the handler
|
||||
for each file rooted in root that passes the filters. It runs as its own
|
||||
thread, so must be started as such::
|
||||
|
||||
notifier = ThreadedNotifier('c:\\', handler = StreamHandler())
|
||||
notifier.start()
|
||||
C:\Autoexec.bat changed.
|
||||
"""
|
||||
def __init__(self, root='.', filters=[], handler=lambda file: None):
|
||||
# init notifier stuff
|
||||
BlockingNotifier.__init__(self, root, filters)
|
||||
# init thread stuff
|
||||
Thread.__init__(self)
|
||||
# set it as a daemon thread so that it doesn't block waiting to close.
|
||||
# I tried setting __del__(self) to .quit(), but unfortunately, there
|
||||
# are references to this object in the win32api stuff, so __del__
|
||||
# never gets called.
|
||||
self.setDaemon(True)
|
||||
|
||||
self.handle = handler
|
||||
|
||||
def run(self):
|
||||
for file in self.get_changed_files():
|
||||
self.handle(file)
|
||||
|
||||
|
||||
class StreamHandler(object):
|
||||
"""
|
||||
StreamHandler: a sample handler object for use with the threaded
|
||||
notifier that will announce by writing to the supplied stream
|
||||
(stdout by default) the name of the file.
|
||||
"""
|
||||
def __init__(self, output=sys.stdout):
|
||||
self.output = output
|
||||
|
||||
def __call__(self, filename):
|
||||
self.output.write('%s changed.\n' % filename)
|
124
libs/win/jaraco/windows/inet.py
Normal file
124
libs/win/jaraco/windows/inet.py
Normal file
|
@ -0,0 +1,124 @@
|
|||
"""
|
||||
Some routines for retrieving the addresses from the local
|
||||
network config.
|
||||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import itertools
|
||||
import ctypes
|
||||
|
||||
|
||||
from jaraco.windows.api import errors, inet
|
||||
|
||||
|
||||
def GetAdaptersAddresses():
|
||||
size = ctypes.c_ulong()
|
||||
res = inet.GetAdaptersAddresses(0, 0, None, None, size)
|
||||
if res != errors.ERROR_BUFFER_OVERFLOW:
|
||||
raise RuntimeError("Error getting structure length (%d)" % res)
|
||||
print(size.value)
|
||||
pointer_type = ctypes.POINTER(inet.IP_ADAPTER_ADDRESSES)
|
||||
buffer = ctypes.create_string_buffer(size.value)
|
||||
struct_p = ctypes.cast(buffer, pointer_type)
|
||||
res = inet.GetAdaptersAddresses(0, 0, None, struct_p, size)
|
||||
if res != errors.NO_ERROR:
|
||||
raise RuntimeError("Error retrieving table (%d)" % res)
|
||||
while struct_p:
|
||||
yield struct_p.contents
|
||||
struct_p = struct_p.contents.next
|
||||
|
||||
|
||||
class AllocatedTable(object):
|
||||
"""
|
||||
Both the interface table and the ip address table use the same
|
||||
technique to store arrays of structures of variable length. This
|
||||
base class captures the functionality to retrieve and access those
|
||||
table entries.
|
||||
|
||||
The subclass needs to define three class attributes:
|
||||
method: a callable that takes three arguments - a pointer to
|
||||
the structure, the length of the data contained by the
|
||||
structure, and a boolean of whether the result should
|
||||
be sorted.
|
||||
structure: a C structure defininition that describes the table
|
||||
format.
|
||||
row_structure: a C structure definition that describes the row
|
||||
format.
|
||||
"""
|
||||
def __get_table_size(self):
|
||||
"""
|
||||
Retrieve the size of the buffer needed by calling the method
|
||||
with a null pointer and length of zero. This should trigger an
|
||||
insufficient buffer error and return the size needed for the
|
||||
buffer.
|
||||
"""
|
||||
length = ctypes.wintypes.DWORD()
|
||||
res = self.method(None, length, False)
|
||||
if res != errors.ERROR_INSUFFICIENT_BUFFER:
|
||||
raise RuntimeError("Error getting table length (%d)" % res)
|
||||
return length.value
|
||||
|
||||
def get_table(self):
|
||||
"""
|
||||
Get the table
|
||||
"""
|
||||
buffer_length = self.__get_table_size()
|
||||
returned_buffer_length = ctypes.wintypes.DWORD(buffer_length)
|
||||
buffer = ctypes.create_string_buffer(buffer_length)
|
||||
pointer_type = ctypes.POINTER(self.structure)
|
||||
table_p = ctypes.cast(buffer, pointer_type)
|
||||
res = self.method(table_p, returned_buffer_length, False)
|
||||
if res != errors.NO_ERROR:
|
||||
raise RuntimeError("Error retrieving table (%d)" % res)
|
||||
return table_p.contents
|
||||
|
||||
@property
|
||||
def entries(self):
|
||||
"""
|
||||
Using the table structure, return the array of entries based
|
||||
on the table size.
|
||||
"""
|
||||
table = self.get_table()
|
||||
entries_array = self.row_structure * table.num_entries
|
||||
pointer_type = ctypes.POINTER(entries_array)
|
||||
return ctypes.cast(table.entries, pointer_type).contents
|
||||
|
||||
|
||||
class InterfaceTable(AllocatedTable):
|
||||
method = inet.GetIfTable
|
||||
structure = inet.MIB_IFTABLE
|
||||
row_structure = inet.MIB_IFROW
|
||||
|
||||
|
||||
class AddressTable(AllocatedTable):
|
||||
method = inet.GetIpAddrTable
|
||||
structure = inet.MIB_IPADDRTABLE
|
||||
row_structure = inet.MIB_IPADDRROW
|
||||
|
||||
|
||||
class AddressManager(object):
|
||||
@staticmethod
|
||||
def hardware_address_to_string(addr):
|
||||
hex_bytes = (byte.encode('hex') for byte in addr)
|
||||
return ':'.join(hex_bytes)
|
||||
|
||||
def get_host_mac_address_strings(self):
|
||||
return (
|
||||
self.hardware_address_to_string(addr)
|
||||
for addr in self.get_host_mac_addresses())
|
||||
|
||||
def get_host_ip_address_strings(self):
|
||||
return itertools.imap(str, self.get_host_ip_addresses())
|
||||
|
||||
def get_host_mac_addresses(self):
|
||||
return (
|
||||
entry.physical_address
|
||||
for entry in InterfaceTable().entries
|
||||
)
|
||||
|
||||
def get_host_ip_addresses(self):
|
||||
return (
|
||||
entry.address
|
||||
for entry in AddressTable().entries
|
||||
)
|
21
libs/win/jaraco/windows/lib.py
Normal file
21
libs/win/jaraco/windows/lib.py
Normal file
|
@ -0,0 +1,21 @@
|
|||
import ctypes
|
||||
|
||||
from .api import library
|
||||
|
||||
|
||||
def find_lib(lib):
|
||||
r"""
|
||||
Find the DLL for a given library.
|
||||
|
||||
Accepts a string or loaded module
|
||||
|
||||
>>> print(find_lib('kernel32').lower())
|
||||
c:\windows\system32\kernel32.dll
|
||||
"""
|
||||
if isinstance(lib, str):
|
||||
lib = getattr(ctypes.windll, lib)
|
||||
|
||||
size = 1024
|
||||
result = ctypes.create_unicode_buffer(size)
|
||||
library.GetModuleFileName(lib._handle, result, size)
|
||||
return result.value
|
29
libs/win/jaraco/windows/memory.py
Normal file
29
libs/win/jaraco/windows/memory.py
Normal file
|
@ -0,0 +1,29 @@
|
|||
import ctypes
|
||||
from ctypes import WinError
|
||||
|
||||
from .api import memory
|
||||
|
||||
|
||||
class LockedMemory(object):
|
||||
def __init__(self, handle):
|
||||
self.handle = handle
|
||||
|
||||
def __enter__(self):
|
||||
self.data_ptr = memory.GlobalLock(self.handle)
|
||||
if not self.data_ptr:
|
||||
del self.data_ptr
|
||||
raise WinError()
|
||||
return self
|
||||
|
||||
def __exit__(self, *args):
|
||||
memory.GlobalUnlock(self.handle)
|
||||
del self.data_ptr
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
with self:
|
||||
return ctypes.string_at(self.data_ptr, self.size)
|
||||
|
||||
@property
|
||||
def size(self):
|
||||
return memory.GlobalSize(self.data_ptr)
|
63
libs/win/jaraco/windows/mmap.py
Normal file
63
libs/win/jaraco/windows/mmap.py
Normal file
|
@ -0,0 +1,63 @@
|
|||
import ctypes.wintypes
|
||||
|
||||
import six
|
||||
|
||||
from .error import handle_nonzero_success
|
||||
from .api import memory
|
||||
|
||||
|
||||
class MemoryMap(object):
|
||||
"""
|
||||
A memory map object which can have security attributes overridden.
|
||||
"""
|
||||
def __init__(self, name, length, security_attributes=None):
|
||||
self.name = name
|
||||
self.length = length
|
||||
self.security_attributes = security_attributes
|
||||
self.pos = 0
|
||||
|
||||
def __enter__(self):
|
||||
p_SA = (
|
||||
ctypes.byref(self.security_attributes)
|
||||
if self.security_attributes else None
|
||||
)
|
||||
INVALID_HANDLE_VALUE = -1
|
||||
PAGE_READWRITE = 0x4
|
||||
FILE_MAP_WRITE = 0x2
|
||||
filemap = ctypes.windll.kernel32.CreateFileMappingW(
|
||||
INVALID_HANDLE_VALUE, p_SA, PAGE_READWRITE, 0, self.length,
|
||||
six.text_type(self.name))
|
||||
handle_nonzero_success(filemap)
|
||||
if filemap == INVALID_HANDLE_VALUE:
|
||||
raise Exception("Failed to create file mapping")
|
||||
self.filemap = filemap
|
||||
self.view = memory.MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, 0)
|
||||
return self
|
||||
|
||||
def seek(self, pos):
|
||||
self.pos = pos
|
||||
|
||||
def write(self, msg):
|
||||
assert isinstance(msg, bytes)
|
||||
n = len(msg)
|
||||
if self.pos + n >= self.length: # A little safety.
|
||||
raise ValueError("Refusing to write %d bytes" % n)
|
||||
dest = self.view + self.pos
|
||||
length = ctypes.c_size_t(n)
|
||||
ctypes.windll.kernel32.RtlMoveMemory(dest, msg, length)
|
||||
self.pos += n
|
||||
|
||||
def read(self, n):
|
||||
"""
|
||||
Read n bytes from mapped view.
|
||||
"""
|
||||
out = ctypes.create_string_buffer(n)
|
||||
source = self.view + self.pos
|
||||
length = ctypes.c_size_t(n)
|
||||
ctypes.windll.kernel32.RtlMoveMemory(out, source, length)
|
||||
self.pos += n
|
||||
return out.raw
|
||||
|
||||
def __exit__(self, exc_type, exc_val, tb):
|
||||
ctypes.windll.kernel32.UnmapViewOfFile(self.view)
|
||||
ctypes.windll.kernel32.CloseHandle(self.filemap)
|
59
libs/win/jaraco/windows/msie.py
Normal file
59
libs/win/jaraco/windows/msie.py
Normal file
|
@ -0,0 +1,59 @@
|
|||
# -*- coding: UTF-8 -*-
|
||||
|
||||
"""cookies.py
|
||||
|
||||
Cookie support utilities
|
||||
"""
|
||||
|
||||
import os
|
||||
import itertools
|
||||
|
||||
import six
|
||||
|
||||
|
||||
class CookieMonster(object):
|
||||
"Read cookies out of a user's IE cookies file"
|
||||
|
||||
@property
|
||||
def cookie_dir(self):
|
||||
import _winreg as winreg
|
||||
key = winreg.OpenKeyEx(
|
||||
winreg.HKEY_CURRENT_USER, 'Software'
|
||||
'\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders')
|
||||
cookie_dir, type = winreg.QueryValueEx(key, 'Cookies')
|
||||
return cookie_dir
|
||||
|
||||
def entries(self, filename):
|
||||
with open(os.path.join(self.cookie_dir, filename)) as cookie_file:
|
||||
while True:
|
||||
entry = itertools.takewhile(
|
||||
self.is_not_cookie_delimiter,
|
||||
cookie_file)
|
||||
entry = list(map(six.text_type.rstrip, entry))
|
||||
if not entry:
|
||||
break
|
||||
cookie = self.make_cookie(*entry)
|
||||
yield cookie
|
||||
|
||||
@staticmethod
|
||||
def is_not_cookie_delimiter(s):
|
||||
return s != '*\n'
|
||||
|
||||
@staticmethod
|
||||
def make_cookie(
|
||||
key, value, domain, flags, ExpireLow, ExpireHigh,
|
||||
CreateLow, CreateHigh):
|
||||
expires = (int(ExpireHigh) << 32) | int(ExpireLow)
|
||||
created = (int(CreateHigh) << 32) | int(CreateLow)
|
||||
flags = int(flags)
|
||||
domain, sep, path = domain.partition('/')
|
||||
path = '/' + path
|
||||
return dict(
|
||||
key=key,
|
||||
value=value,
|
||||
domain=domain,
|
||||
flags=flags,
|
||||
expires=expires,
|
||||
created=created,
|
||||
path=path,
|
||||
)
|
30
libs/win/jaraco/windows/net.py
Normal file
30
libs/win/jaraco/windows/net.py
Normal file
|
@ -0,0 +1,30 @@
|
|||
"""
|
||||
API hooks for network stuff.
|
||||
"""
|
||||
|
||||
__all__ = ('AddConnection')
|
||||
|
||||
from jaraco.windows.error import WindowsError
|
||||
from .api import net
|
||||
|
||||
|
||||
def AddConnection(
|
||||
remote_name, type=net.RESOURCETYPE_ANY, local_name=None,
|
||||
provider_name=None, user=None, password=None, flags=0):
|
||||
resource = net.NETRESOURCE(
|
||||
type=type,
|
||||
remote_name=remote_name,
|
||||
local_name=local_name,
|
||||
provider_name=provider_name,
|
||||
# WNetAddConnection2 ignores the other members of NETRESOURCE
|
||||
)
|
||||
|
||||
result = net.WNetAddConnection2(
|
||||
resource,
|
||||
password,
|
||||
user,
|
||||
flags,
|
||||
)
|
||||
|
||||
if result != 0:
|
||||
raise WindowsError(result)
|
77
libs/win/jaraco/windows/power.py
Normal file
77
libs/win/jaraco/windows/power.py
Normal file
|
@ -0,0 +1,77 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import itertools
|
||||
import contextlib
|
||||
|
||||
from more_itertools.recipes import consume, unique_justseen
|
||||
try:
|
||||
import wmi as wmilib
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
from jaraco.windows.error import handle_nonzero_success
|
||||
from .api import power
|
||||
|
||||
|
||||
def GetSystemPowerStatus():
|
||||
stat = power.SYSTEM_POWER_STATUS()
|
||||
handle_nonzero_success(GetSystemPowerStatus(stat))
|
||||
return stat
|
||||
|
||||
|
||||
def _init_power_watcher():
|
||||
global power_watcher
|
||||
if 'power_watcher' not in globals():
|
||||
wmi = wmilib.WMI()
|
||||
query = 'SELECT * from Win32_PowerManagementEvent'
|
||||
power_watcher = wmi.ExecNotificationQuery(query)
|
||||
|
||||
|
||||
def get_power_management_events():
|
||||
_init_power_watcher()
|
||||
while True:
|
||||
yield power_watcher.NextEvent()
|
||||
|
||||
|
||||
def wait_for_power_status_change():
|
||||
EVT_POWER_STATUS_CHANGE = 10
|
||||
|
||||
def not_power_status_change(evt):
|
||||
return evt.EventType != EVT_POWER_STATUS_CHANGE
|
||||
events = get_power_management_events()
|
||||
consume(itertools.takewhile(not_power_status_change, events))
|
||||
|
||||
|
||||
def get_unique_power_states():
|
||||
"""
|
||||
Just like get_power_states, but ensures values are returned only
|
||||
when the state changes.
|
||||
"""
|
||||
return unique_justseen(get_power_states())
|
||||
|
||||
|
||||
def get_power_states():
|
||||
"""
|
||||
Continuously return the power state of the system when it changes.
|
||||
This function will block indefinitely if the power state never
|
||||
changes.
|
||||
"""
|
||||
while True:
|
||||
state = GetSystemPowerStatus()
|
||||
yield state.ac_line_status_string
|
||||
wait_for_power_status_change()
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def no_sleep():
|
||||
"""
|
||||
Context that prevents the computer from going to sleep.
|
||||
"""
|
||||
mode = power.ES.continuous | power.ES.system_required
|
||||
handle_nonzero_success(power.SetThreadExecutionState(mode))
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
handle_nonzero_success(power.SetThreadExecutionState(power.ES.continuous))
|
142
libs/win/jaraco/windows/privilege.py
Normal file
142
libs/win/jaraco/windows/privilege.py
Normal file
|
@ -0,0 +1,142 @@
|
|||
from __future__ import print_function
|
||||
|
||||
import ctypes
|
||||
from ctypes import wintypes
|
||||
|
||||
from .api import security
|
||||
from .api import privilege
|
||||
from .api import process
|
||||
|
||||
|
||||
def get_process_token():
|
||||
"""
|
||||
Get the current process token
|
||||
"""
|
||||
token = wintypes.HANDLE()
|
||||
res = process.OpenProcessToken(
|
||||
process.GetCurrentProcess(), process.TOKEN_ALL_ACCESS, token)
|
||||
if not res > 0:
|
||||
raise RuntimeError("Couldn't get process token")
|
||||
return token
|
||||
|
||||
|
||||
def get_symlink_luid():
|
||||
"""
|
||||
Get the LUID for the SeCreateSymbolicLinkPrivilege
|
||||
"""
|
||||
symlink_luid = privilege.LUID()
|
||||
res = privilege.LookupPrivilegeValue(
|
||||
None, "SeCreateSymbolicLinkPrivilege", symlink_luid)
|
||||
if not res > 0:
|
||||
raise RuntimeError("Couldn't lookup privilege value")
|
||||
return symlink_luid
|
||||
|
||||
|
||||
def get_privilege_information():
|
||||
"""
|
||||
Get all privileges associated with the current process.
|
||||
"""
|
||||
# first call with zero length to determine what size buffer we need
|
||||
|
||||
return_length = wintypes.DWORD()
|
||||
params = [
|
||||
get_process_token(),
|
||||
privilege.TOKEN_INFORMATION_CLASS.TokenPrivileges,
|
||||
None,
|
||||
0,
|
||||
return_length,
|
||||
]
|
||||
|
||||
res = privilege.GetTokenInformation(*params)
|
||||
|
||||
# assume we now have the necessary length in return_length
|
||||
|
||||
buffer = ctypes.create_string_buffer(return_length.value)
|
||||
params[2] = buffer
|
||||
params[3] = return_length.value
|
||||
|
||||
res = privilege.GetTokenInformation(*params)
|
||||
assert res > 0, "Error in second GetTokenInformation (%d)" % res
|
||||
|
||||
privileges = ctypes.cast(
|
||||
buffer, ctypes.POINTER(privilege.TOKEN_PRIVILEGES)).contents
|
||||
return privileges
|
||||
|
||||
|
||||
def report_privilege_information():
|
||||
"""
|
||||
Report all privilege information assigned to the current process.
|
||||
"""
|
||||
privileges = get_privilege_information()
|
||||
print("found {0} privileges".format(privileges.count))
|
||||
tuple(map(print, privileges))
|
||||
|
||||
|
||||
def enable_symlink_privilege():
|
||||
"""
|
||||
Try to assign the symlink privilege to the current process token.
|
||||
Return True if the assignment is successful.
|
||||
"""
|
||||
# create a space in memory for a TOKEN_PRIVILEGES structure
|
||||
# with one element
|
||||
size = ctypes.sizeof(privilege.TOKEN_PRIVILEGES)
|
||||
size += ctypes.sizeof(privilege.LUID_AND_ATTRIBUTES)
|
||||
buffer = ctypes.create_string_buffer(size)
|
||||
tp = ctypes.cast(buffer, ctypes.POINTER(privilege.TOKEN_PRIVILEGES)).contents
|
||||
tp.count = 1
|
||||
tp.get_array()[0].enable()
|
||||
tp.get_array()[0].LUID = get_symlink_luid()
|
||||
token = get_process_token()
|
||||
res = privilege.AdjustTokenPrivileges(token, False, tp, 0, None, None)
|
||||
if res == 0:
|
||||
raise RuntimeError("Error in AdjustTokenPrivileges")
|
||||
|
||||
ERROR_NOT_ALL_ASSIGNED = 1300
|
||||
return ctypes.windll.kernel32.GetLastError() != ERROR_NOT_ALL_ASSIGNED
|
||||
|
||||
|
||||
class PolicyHandle(wintypes.HANDLE):
|
||||
pass
|
||||
|
||||
|
||||
class LSA_UNICODE_STRING(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('length', ctypes.c_ushort),
|
||||
('max_length', ctypes.c_ushort),
|
||||
('buffer', ctypes.wintypes.LPWSTR),
|
||||
]
|
||||
|
||||
|
||||
def OpenPolicy(system_name, object_attributes, access_mask):
|
||||
policy = PolicyHandle()
|
||||
raise NotImplementedError(
|
||||
"Need to construct structures for parameters "
|
||||
"(see http://msdn.microsoft.com/en-us/library/windows"
|
||||
"/desktop/aa378299%28v=vs.85%29.aspx)")
|
||||
res = ctypes.windll.advapi32.LsaOpenPolicy(
|
||||
system_name, object_attributes,
|
||||
access_mask, ctypes.byref(policy))
|
||||
assert res == 0, "Error status {res}".format(**vars())
|
||||
return policy
|
||||
|
||||
|
||||
def grant_symlink_privilege(who, machine=''):
|
||||
"""
|
||||
Grant the 'create symlink' privilege to who.
|
||||
|
||||
Based on http://support.microsoft.com/kb/132958
|
||||
"""
|
||||
flags = security.POLICY_CREATE_ACCOUNT | security.POLICY_LOOKUP_NAMES
|
||||
policy = OpenPolicy(machine, flags)
|
||||
return policy
|
||||
|
||||
|
||||
def main():
|
||||
assigned = enable_symlink_privilege()
|
||||
msg = ['failure', 'success'][assigned]
|
||||
|
||||
print("Symlink privilege assignment completed with {0}".format(msg))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
20
libs/win/jaraco/windows/registry.py
Normal file
20
libs/win/jaraco/windows/registry.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
from itertools import count
|
||||
|
||||
import six
|
||||
winreg = six.moves.winreg
|
||||
|
||||
|
||||
def key_values(key):
|
||||
for index in count():
|
||||
try:
|
||||
yield winreg.EnumValue(key, index)
|
||||
except WindowsError:
|
||||
break
|
||||
|
||||
|
||||
def key_subkeys(key):
|
||||
for index in count():
|
||||
try:
|
||||
yield winreg.EnumKey(key, index)
|
||||
except WindowsError:
|
||||
break
|
35
libs/win/jaraco/windows/reparse.py
Normal file
35
libs/win/jaraco/windows/reparse.py
Normal file
|
@ -0,0 +1,35 @@
|
|||
from __future__ import division
|
||||
|
||||
import ctypes.wintypes
|
||||
|
||||
from .error import handle_nonzero_success
|
||||
from .api import filesystem
|
||||
|
||||
|
||||
def DeviceIoControl(
|
||||
device, io_control_code, in_buffer, out_buffer, overlapped=None):
|
||||
if overlapped is not None:
|
||||
raise NotImplementedError("overlapped handles not yet supported")
|
||||
|
||||
if isinstance(out_buffer, int):
|
||||
out_buffer = ctypes.create_string_buffer(out_buffer)
|
||||
|
||||
in_buffer_size = len(in_buffer) if in_buffer is not None else 0
|
||||
out_buffer_size = len(out_buffer)
|
||||
assert isinstance(out_buffer, ctypes.Array)
|
||||
|
||||
returned_bytes = ctypes.wintypes.DWORD()
|
||||
|
||||
res = filesystem.DeviceIoControl(
|
||||
device,
|
||||
io_control_code,
|
||||
in_buffer, in_buffer_size,
|
||||
out_buffer, out_buffer_size,
|
||||
returned_bytes,
|
||||
overlapped,
|
||||
)
|
||||
|
||||
handle_nonzero_success(res)
|
||||
handle_nonzero_success(returned_bytes)
|
||||
|
||||
return out_buffer[:returned_bytes.value]
|
67
libs/win/jaraco/windows/security.py
Normal file
67
libs/win/jaraco/windows/security.py
Normal file
|
@ -0,0 +1,67 @@
|
|||
import ctypes.wintypes
|
||||
|
||||
from jaraco.windows.error import handle_nonzero_success
|
||||
from .api import security
|
||||
|
||||
|
||||
def GetTokenInformation(token, information_class):
|
||||
"""
|
||||
Given a token, get the token information for it.
|
||||
"""
|
||||
data_size = ctypes.wintypes.DWORD()
|
||||
ctypes.windll.advapi32.GetTokenInformation(
|
||||
token, information_class.num,
|
||||
0, 0, ctypes.byref(data_size))
|
||||
data = ctypes.create_string_buffer(data_size.value)
|
||||
handle_nonzero_success(ctypes.windll.advapi32.GetTokenInformation(
|
||||
token,
|
||||
information_class.num,
|
||||
ctypes.byref(data), ctypes.sizeof(data),
|
||||
ctypes.byref(data_size)))
|
||||
return ctypes.cast(data, ctypes.POINTER(security.TOKEN_USER)).contents
|
||||
|
||||
|
||||
def OpenProcessToken(proc_handle, access):
|
||||
result = ctypes.wintypes.HANDLE()
|
||||
proc_handle = ctypes.wintypes.HANDLE(proc_handle)
|
||||
handle_nonzero_success(ctypes.windll.advapi32.OpenProcessToken(
|
||||
proc_handle, access, ctypes.byref(result)))
|
||||
return result
|
||||
|
||||
|
||||
def get_current_user():
|
||||
"""
|
||||
Return a TOKEN_USER for the owner of this process.
|
||||
"""
|
||||
process = OpenProcessToken(
|
||||
ctypes.windll.kernel32.GetCurrentProcess(),
|
||||
security.TokenAccess.TOKEN_QUERY,
|
||||
)
|
||||
return GetTokenInformation(process, security.TOKEN_USER)
|
||||
|
||||
|
||||
def get_security_attributes_for_user(user=None):
|
||||
"""
|
||||
Return a SECURITY_ATTRIBUTES structure with the SID set to the
|
||||
specified user (uses current user if none is specified).
|
||||
"""
|
||||
if user is None:
|
||||
user = get_current_user()
|
||||
|
||||
assert isinstance(user, security.TOKEN_USER), (
|
||||
"user must be TOKEN_USER instance")
|
||||
|
||||
SD = security.SECURITY_DESCRIPTOR()
|
||||
SA = security.SECURITY_ATTRIBUTES()
|
||||
# by attaching the actual security descriptor, it will be garbage-
|
||||
# collected with the security attributes
|
||||
SA.descriptor = SD
|
||||
SA.bInheritHandle = 1
|
||||
|
||||
ctypes.windll.advapi32.InitializeSecurityDescriptor(
|
||||
ctypes.byref(SD),
|
||||
security.SECURITY_DESCRIPTOR.REVISION)
|
||||
ctypes.windll.advapi32.SetSecurityDescriptorOwner(
|
||||
ctypes.byref(SD),
|
||||
user.SID, 0)
|
||||
return SA
|
236
libs/win/jaraco/windows/services.py
Normal file
236
libs/win/jaraco/windows/services.py
Normal file
|
@ -0,0 +1,236 @@
|
|||
"""
|
||||
Windows Services support for controlling Windows Services.
|
||||
|
||||
Based on http://code.activestate.com
|
||||
/recipes/115875-controlling-windows-services/
|
||||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import sys
|
||||
import time
|
||||
|
||||
import win32api
|
||||
import win32con
|
||||
import win32service
|
||||
|
||||
|
||||
class Service(object):
|
||||
"""
|
||||
The Service Class is used for controlling Windows
|
||||
services. Just pass the name of the service you wish to control to the
|
||||
class instance and go from there. For example, if you want to control
|
||||
the Workstation service try this:
|
||||
|
||||
from jaraco.windows import services
|
||||
workstation = services.Service("Workstation")
|
||||
workstation.start()
|
||||
workstation.fetchstatus("running", 10)
|
||||
workstation.stop()
|
||||
workstation.fetchstatus("stopped")
|
||||
|
||||
Creating an instance of the Service class is done by passing the name of
|
||||
the service as it appears in the Management Console or the short name as
|
||||
it appears in the registry. Mixed case is ok.
|
||||
cvs = services.Service("CVS NT Service 1.11.1.2 (Build 41)")
|
||||
or
|
||||
cvs = services.Service("cvs")
|
||||
|
||||
If needing remote service control try this:
|
||||
cvs = services.Service("cvs", r"\\CVS_SERVER")
|
||||
or
|
||||
cvs = services.Service("cvs", "\\\\CVS_SERVER")
|
||||
|
||||
The Service Class supports these methods:
|
||||
|
||||
start: Starts service.
|
||||
stop: Stops service.
|
||||
restart: Stops and restarts service.
|
||||
pause: Pauses service (Only if service supports feature).
|
||||
resume: Resumes service that has been paused.
|
||||
status: Queries current status of service.
|
||||
fetchstatus: Continually queries service until requested
|
||||
status(STARTING, RUNNING,
|
||||
STOPPING & STOPPED) is met or timeout value(in seconds) reached.
|
||||
Default timeout value is infinite.
|
||||
infotype: Queries service for process type. (Single, shared and/or
|
||||
interactive process)
|
||||
infoctrl: Queries control information about a running service.
|
||||
i.e. Can it be paused, stopped, etc?
|
||||
infostartup: Queries service Startup type. (Boot, System,
|
||||
Automatic, Manual, Disabled)
|
||||
setstartup Changes/sets Startup type. (Boot, System,
|
||||
Automatic, Manual, Disabled)
|
||||
getname: Gets the long and short service names used by Windowin32service.
|
||||
(Generally used for internal purposes)
|
||||
"""
|
||||
|
||||
def __init__(self, service, machinename=None, dbname=None):
|
||||
self.userv = service
|
||||
self.scmhandle = win32service.OpenSCManager(
|
||||
machinename, dbname, win32service.SC_MANAGER_ALL_ACCESS)
|
||||
self.sserv, self.lserv = self.getname()
|
||||
if (self.sserv or self.lserv) is None:
|
||||
sys.exit()
|
||||
self.handle = win32service.OpenService(
|
||||
self.scmhandle, self.sserv, win32service.SERVICE_ALL_ACCESS)
|
||||
self.sccss = "SYSTEM\\CurrentControlSet\\Services\\"
|
||||
|
||||
def start(self):
|
||||
win32service.StartService(self.handle, None)
|
||||
|
||||
def stop(self):
|
||||
self.stat = win32service.ControlService(
|
||||
self.handle, win32service.SERVICE_CONTROL_STOP)
|
||||
|
||||
def restart(self):
|
||||
self.stop()
|
||||
self.fetchstatus("STOPPED")
|
||||
self.start()
|
||||
|
||||
def pause(self):
|
||||
self.stat = win32service.ControlService(
|
||||
self.handle, win32service.SERVICE_CONTROL_PAUSE)
|
||||
|
||||
def resume(self):
|
||||
self.stat = win32service.ControlService(
|
||||
self.handle, win32service.SERVICE_CONTROL_CONTINUE)
|
||||
|
||||
def status(self, prn=0):
|
||||
self.stat = win32service.QueryServiceStatus(self.handle)
|
||||
if self.stat[1] == win32service.SERVICE_STOPPED:
|
||||
if prn == 1:
|
||||
print("The %s service is stopped." % self.lserv)
|
||||
else:
|
||||
return "STOPPED"
|
||||
elif self.stat[1] == win32service.SERVICE_START_PENDING:
|
||||
if prn == 1:
|
||||
print("The %s service is starting." % self.lserv)
|
||||
else:
|
||||
return "STARTING"
|
||||
elif self.stat[1] == win32service.SERVICE_STOP_PENDING:
|
||||
if prn == 1:
|
||||
print("The %s service is stopping." % self.lserv)
|
||||
else:
|
||||
return "STOPPING"
|
||||
elif self.stat[1] == win32service.SERVICE_RUNNING:
|
||||
if prn == 1:
|
||||
print("The %s service is running." % self.lserv)
|
||||
else:
|
||||
return "RUNNING"
|
||||
|
||||
def fetchstatus(self, fstatus, timeout=None):
|
||||
self.fstatus = fstatus.upper()
|
||||
if timeout is not None:
|
||||
timeout = int(timeout)
|
||||
timeout *= 2
|
||||
|
||||
def to(timeout):
|
||||
time.sleep(.5)
|
||||
if timeout is not None:
|
||||
if timeout > 1:
|
||||
timeout -= 1
|
||||
return timeout
|
||||
else:
|
||||
return "TO"
|
||||
if self.fstatus == "STOPPED":
|
||||
while 1:
|
||||
self.stat = win32service.QueryServiceStatus(self.handle)
|
||||
if self.stat[1] == win32service.SERVICE_STOPPED:
|
||||
self.fstate = "STOPPED"
|
||||
break
|
||||
else:
|
||||
timeout = to(timeout)
|
||||
if timeout == "TO":
|
||||
return "TIMEDOUT"
|
||||
break
|
||||
elif self.fstatus == "STOPPING":
|
||||
while 1:
|
||||
self.stat = win32service.QueryServiceStatus(self.handle)
|
||||
if self.stat[1]==win32service.SERVICE_STOP_PENDING:
|
||||
self.fstate = "STOPPING"
|
||||
break
|
||||
else:
|
||||
timeout=to(timeout)
|
||||
if timeout == "TO":
|
||||
return "TIMEDOUT"
|
||||
break
|
||||
elif self.fstatus == "RUNNING":
|
||||
while 1:
|
||||
self.stat = win32service.QueryServiceStatus(self.handle)
|
||||
if self.stat[1]==win32service.SERVICE_RUNNING:
|
||||
self.fstate = "RUNNING"
|
||||
break
|
||||
else:
|
||||
timeout=to(timeout)
|
||||
if timeout == "TO":
|
||||
return "TIMEDOUT"
|
||||
break
|
||||
elif self.fstatus == "STARTING":
|
||||
while 1:
|
||||
self.stat = win32service.QueryServiceStatus(self.handle)
|
||||
if self.stat[1]==win32service.SERVICE_START_PENDING:
|
||||
self.fstate = "STARTING"
|
||||
break
|
||||
else:
|
||||
timeout=to(timeout)
|
||||
if timeout == "TO":
|
||||
return "TIMEDOUT"
|
||||
break
|
||||
|
||||
def infotype(self):
|
||||
self.stat = win32service.QueryServiceStatus(self.handle)
|
||||
if self.stat[0] and win32service.SERVICE_WIN32_OWN_PROCESS:
|
||||
print("The %s service runs in its own process." % self.lserv)
|
||||
if self.stat[0] and win32service.SERVICE_WIN32_SHARE_PROCESS:
|
||||
print("The %s service shares a process with other services." % self.lserv)
|
||||
if self.stat[0] and win32service.SERVICE_INTERACTIVE_PROCESS:
|
||||
print("The %s service can interact with the desktop." % self.lserv)
|
||||
|
||||
def infoctrl(self):
|
||||
self.stat = win32service.QueryServiceStatus(self.handle)
|
||||
if self.stat[2] and win32service.SERVICE_ACCEPT_PAUSE_CONTINUE:
|
||||
print("The %s service can be paused." % self.lserv)
|
||||
if self.stat[2] and win32service.SERVICE_ACCEPT_STOP:
|
||||
print("The %s service can be stopped." % self.lserv)
|
||||
if self.stat[2] and win32service.SERVICE_ACCEPT_SHUTDOWN:
|
||||
print("The %s service can be shutdown." % self.lserv)
|
||||
|
||||
def infostartup(self):
|
||||
self.isuphandle = win32api.RegOpenKeyEx(win32con.HKEY_LOCAL_MACHINE, self.sccss + self.sserv, 0, win32con.KEY_READ)
|
||||
self.isuptype = win32api.RegQueryValueEx(self.isuphandle, "Start")[0]
|
||||
win32api.RegCloseKey(self.isuphandle)
|
||||
if self.isuptype == 0:
|
||||
return "boot"
|
||||
elif self.isuptype == 1:
|
||||
return "system"
|
||||
elif self.isuptype == 2:
|
||||
return "automatic"
|
||||
elif self.isuptype == 3:
|
||||
return "manual"
|
||||
elif self.isuptype == 4:
|
||||
return "disabled"
|
||||
|
||||
@property
|
||||
def suptype(self):
|
||||
types = 'boot', 'system', 'automatic', 'manual', 'disabled'
|
||||
lookup = dict((name, number) for number, name in enumerate(types))
|
||||
return lookup[self.startuptype]
|
||||
|
||||
def setstartup(self, startuptype):
|
||||
self.startuptype = startuptype.lower()
|
||||
self.snc = win32service.SERVICE_NO_CHANGE
|
||||
win32service.ChangeServiceConfig(self.handle, self.snc, self.suptype,
|
||||
self.snc, None, None, 0, None, None, None, self.lserv)
|
||||
|
||||
def getname(self):
|
||||
self.snames=win32service.EnumServicesStatus(self.scmhandle)
|
||||
for i in self.snames:
|
||||
if i[0].lower() == self.userv.lower():
|
||||
return i[0], i[1]
|
||||
break
|
||||
if i[1].lower() == self.userv.lower():
|
||||
return i[0], i[1]
|
||||
break
|
||||
print("Error: The %s service doesn't seem to exist." % self.userv)
|
||||
return None, None
|
14
libs/win/jaraco/windows/shell.py
Normal file
14
libs/win/jaraco/windows/shell.py
Normal file
|
@ -0,0 +1,14 @@
|
|||
from .api import shell
|
||||
|
||||
|
||||
def get_recycle_bin_confirm():
|
||||
settings = shell.SHELLSTATE()
|
||||
shell.SHGetSetSettings(settings, shell.SSF_NOCONFIRMRECYCLE, False)
|
||||
return not settings.no_confirm_recycle
|
||||
|
||||
|
||||
def set_recycle_bin_confirm(confirm=False):
|
||||
settings = shell.SHELLSTATE()
|
||||
settings.no_confirm_recycle = not confirm
|
||||
shell.SHGetSetSettings(settings, shell.SSF_NOCONFIRMRECYCLE, True)
|
||||
# cross fingers and hope it worked
|
71
libs/win/jaraco/windows/timers.py
Normal file
71
libs/win/jaraco/windows/timers.py
Normal file
|
@ -0,0 +1,71 @@
|
|||
# -*- coding: UTF-8 -*-
|
||||
|
||||
"""
|
||||
timers
|
||||
In particular, contains a waitable timer.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import time
|
||||
from six.moves import _thread
|
||||
|
||||
from jaraco.windows.api import event as win32event
|
||||
|
||||
__author__ = 'Jason R. Coombs <jaraco@jaraco.com>'
|
||||
|
||||
|
||||
class WaitableTimer:
|
||||
"""
|
||||
t = WaitableTimer()
|
||||
t.set(None, 10) # every 10 seconds
|
||||
t.wait_for_signal() # 10 seconds elapses
|
||||
t.stop()
|
||||
t.wait_for_signal(20) # 20 seconds elapses (timeout occurred)
|
||||
"""
|
||||
def __init__(self):
|
||||
self.signal_event = win32event.CreateEvent(None, 0, 0, None)
|
||||
self.stop_event = win32event.CreateEvent(None, 0, 0, None)
|
||||
|
||||
def set(self, due_time, period):
|
||||
_thread.start_new_thread(self._signal_loop, (due_time, period))
|
||||
|
||||
def stop(self):
|
||||
win32event.SetEvent(self.stop_event)
|
||||
|
||||
def wait_for_signal(self, timeout=None):
|
||||
"""
|
||||
wait for the signal; return after the signal has occurred or the
|
||||
timeout in seconds elapses.
|
||||
"""
|
||||
timeout_ms = int(timeout * 1000) if timeout else win32event.INFINITE
|
||||
win32event.WaitForSingleObject(self.signal_event, timeout_ms)
|
||||
|
||||
def _signal_loop(self, due_time, period):
|
||||
if not due_time and not period:
|
||||
raise ValueError("due_time or period must be non-zero")
|
||||
try:
|
||||
if not due_time:
|
||||
due_time = time.time() + period
|
||||
if due_time:
|
||||
self._wait(due_time - time.time())
|
||||
while period:
|
||||
due_time += period
|
||||
self._wait(due_time - time.time())
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _wait(self, seconds):
|
||||
milliseconds = int(seconds * 1000)
|
||||
if milliseconds > 0:
|
||||
res = win32event.WaitForSingleObject(self.stop_event, milliseconds)
|
||||
if res == win32event.WAIT_OBJECT_0:
|
||||
raise Exception
|
||||
if res == win32event.WAIT_TIMEOUT:
|
||||
pass
|
||||
win32event.SetEvent(self.signal_event)
|
||||
|
||||
@staticmethod
|
||||
def get_even_due_time(period):
|
||||
now = time.time()
|
||||
return now - (now % period)
|
254
libs/win/jaraco/windows/timezone.py
Normal file
254
libs/win/jaraco/windows/timezone.py
Normal file
|
@ -0,0 +1,254 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import operator
|
||||
import ctypes
|
||||
import datetime
|
||||
from ctypes.wintypes import WORD, WCHAR, BOOL, LONG
|
||||
|
||||
from jaraco.windows.util import Extended
|
||||
from jaraco.collections import RangeMap
|
||||
|
||||
|
||||
class AnyDict(object):
|
||||
"A dictionary that returns the same value regardless of key"
|
||||
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self.value
|
||||
|
||||
|
||||
class SYSTEMTIME(Extended, ctypes.Structure):
|
||||
_fields_ = [
|
||||
('year', WORD),
|
||||
('month', WORD),
|
||||
('day_of_week', WORD),
|
||||
('day', WORD),
|
||||
('hour', WORD),
|
||||
('minute', WORD),
|
||||
('second', WORD),
|
||||
('millisecond', WORD),
|
||||
]
|
||||
|
||||
|
||||
class REG_TZI_FORMAT(Extended, ctypes.Structure):
|
||||
_fields_ = [
|
||||
('bias', LONG),
|
||||
('standard_bias', LONG),
|
||||
('daylight_bias', LONG),
|
||||
('standard_start', SYSTEMTIME),
|
||||
('daylight_start', SYSTEMTIME),
|
||||
]
|
||||
|
||||
|
||||
class TIME_ZONE_INFORMATION(Extended, ctypes.Structure):
|
||||
_fields_ = [
|
||||
('bias', LONG),
|
||||
('standard_name', WCHAR * 32),
|
||||
('standard_start', SYSTEMTIME),
|
||||
('standard_bias', LONG),
|
||||
('daylight_name', WCHAR * 32),
|
||||
('daylight_start', SYSTEMTIME),
|
||||
('daylight_bias', LONG),
|
||||
]
|
||||
|
||||
|
||||
class DYNAMIC_TIME_ZONE_INFORMATION(TIME_ZONE_INFORMATION):
|
||||
"""
|
||||
Because the structure of the DYNAMIC_TIME_ZONE_INFORMATION extends
|
||||
the structure of the TIME_ZONE_INFORMATION, this structure
|
||||
can be used as a drop-in replacement for calls where the
|
||||
structure is passed by reference.
|
||||
|
||||
For example,
|
||||
dynamic_tzi = DYNAMIC_TIME_ZONE_INFORMATION()
|
||||
ctypes.windll.kernel32.GetTimeZoneInformation(ctypes.byref(dynamic_tzi))
|
||||
|
||||
(although the key_name and dynamic_daylight_time_disabled flags will be
|
||||
set to the default (null)).
|
||||
|
||||
>>> isinstance(DYNAMIC_TIME_ZONE_INFORMATION(), TIME_ZONE_INFORMATION)
|
||||
True
|
||||
|
||||
|
||||
"""
|
||||
_fields_ = [
|
||||
# ctypes automatically includes the fields from the parent
|
||||
('key_name', WCHAR * 128),
|
||||
('dynamic_daylight_time_disabled', BOOL),
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Allow initialization from args from both this class and
|
||||
its superclass. Default ctypes implementation seems to
|
||||
assume that this class is only initialized with its own
|
||||
_fields_ (for non-keyword-args)."""
|
||||
super_self = super(DYNAMIC_TIME_ZONE_INFORMATION, self)
|
||||
super_fields = super_self._fields_
|
||||
super_args = args[:len(super_fields)]
|
||||
self_args = args[len(super_fields):]
|
||||
# convert the super args to keyword args so they're also handled
|
||||
for field, arg in zip(super_fields, super_args):
|
||||
field_name, spec = field
|
||||
kwargs[field_name] = arg
|
||||
super(DYNAMIC_TIME_ZONE_INFORMATION, self).__init__(*self_args, **kwargs)
|
||||
|
||||
|
||||
class Info(DYNAMIC_TIME_ZONE_INFORMATION):
|
||||
"""
|
||||
A time zone definition class based on the win32
|
||||
DYNAMIC_TIME_ZONE_INFORMATION structure.
|
||||
|
||||
Describes a bias against UTC (bias), and two dates at which a separate
|
||||
additional bias applies (standard_bias and daylight_bias).
|
||||
"""
|
||||
|
||||
def field_names(self):
|
||||
return map(operator.itemgetter(0), self._fields_)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""
|
||||
Try to construct a timezone.Info from
|
||||
a) [DYNAMIC_]TIME_ZONE_INFORMATION args
|
||||
b) another Info
|
||||
c) a REG_TZI_FORMAT
|
||||
d) a byte structure
|
||||
"""
|
||||
funcs = (
|
||||
super(Info, self).__init__,
|
||||
self.__init_from_other,
|
||||
self.__init_from_reg_tzi,
|
||||
self.__init_from_bytes,
|
||||
)
|
||||
for func in funcs:
|
||||
try:
|
||||
func(*args, **kwargs)
|
||||
return
|
||||
except TypeError:
|
||||
pass
|
||||
raise TypeError("Invalid arguments for %s" % self.__class__)
|
||||
|
||||
def __init_from_bytes(self, bytes, **kwargs):
|
||||
reg_tzi = REG_TZI_FORMAT()
|
||||
# todo: use buffer API in Python 3
|
||||
buffer = memoryview(bytes)
|
||||
ctypes.memmove(ctypes.addressof(reg_tzi), buffer, len(buffer))
|
||||
self.__init_from_reg_tzi(self, reg_tzi, **kwargs)
|
||||
|
||||
def __init_from_reg_tzi(self, reg_tzi, **kwargs):
|
||||
if not isinstance(reg_tzi, REG_TZI_FORMAT):
|
||||
raise TypeError("Not a REG_TZI_FORMAT")
|
||||
for field_name, type in reg_tzi._fields_:
|
||||
setattr(self, field_name, getattr(reg_tzi, field_name))
|
||||
for name, value in kwargs.items():
|
||||
setattr(self, name, value)
|
||||
|
||||
def __init_from_other(self, other):
|
||||
if not isinstance(other, TIME_ZONE_INFORMATION):
|
||||
raise TypeError("Not a TIME_ZONE_INFORMATION")
|
||||
for name in other.field_names():
|
||||
# explicitly get the value from the underlying structure
|
||||
value = super(Info, other).__getattribute__(other, name)
|
||||
setattr(self, name, value)
|
||||
# consider instead of the loop above just copying the memory directly
|
||||
# size = max(ctypes.sizeof(DYNAMIC_TIME_ZONE_INFO), ctypes.sizeof(other))
|
||||
# ctypes.memmove(ctypes.addressof(self), other, size)
|
||||
|
||||
def __getattribute__(self, attr):
|
||||
value = super(Info, self).__getattribute__(attr)
|
||||
|
||||
def make_minute_timedelta(m):
|
||||
datetime.timedelta(minutes=m)
|
||||
if 'bias' in attr:
|
||||
value = make_minute_timedelta(value)
|
||||
return value
|
||||
|
||||
@classmethod
|
||||
def current(class_):
|
||||
"Windows Platform SDK GetTimeZoneInformation"
|
||||
tzi = class_()
|
||||
kernel32 = ctypes.windll.kernel32
|
||||
getter = kernel32.GetTimeZoneInformation
|
||||
getter = getattr(kernel32, 'GetDynamicTimeZoneInformation', getter)
|
||||
code = getter(ctypes.byref(tzi))
|
||||
return code, tzi
|
||||
|
||||
def set(self):
|
||||
kernel32 = ctypes.windll.kernel32
|
||||
setter = kernel32.SetTimeZoneInformation
|
||||
setter = getattr(kernel32, 'SetDynamicTimeZoneInformation', setter)
|
||||
return setter(ctypes.byref(self))
|
||||
|
||||
def copy(self):
|
||||
return self.__class__(self)
|
||||
|
||||
def locate_daylight_start(self, year):
|
||||
info = self.get_info_for_year(year)
|
||||
return self._locate_day(year, info.daylight_start)
|
||||
|
||||
def locate_standard_start(self, year):
|
||||
info = self.get_info_for_year(year)
|
||||
return self._locate_day(year, info.standard_start)
|
||||
|
||||
def get_info_for_year(self, year):
|
||||
return self.dynamic_info[year]
|
||||
|
||||
@property
|
||||
def dynamic_info(self):
|
||||
"Return a map that for a given year will return the correct Info"
|
||||
if self.key_name:
|
||||
dyn_key = self.get_key().subkey('Dynamic DST')
|
||||
del dyn_key['FirstEntry']
|
||||
del dyn_key['LastEntry']
|
||||
years = map(int, dyn_key.keys())
|
||||
values = map(Info, dyn_key.values())
|
||||
# create a range mapping that searches by descending year and matches
|
||||
# if the target year is greater or equal.
|
||||
return RangeMap(zip(years, values), RangeMap.descending, operator.ge)
|
||||
else:
|
||||
return AnyDict(self)
|
||||
|
||||
@staticmethod
|
||||
def _locate_day(year, cutoff):
|
||||
"""
|
||||
Takes a SYSTEMTIME object, such as retrieved from a TIME_ZONE_INFORMATION
|
||||
structure or call to GetTimeZoneInformation and interprets
|
||||
it based on the given
|
||||
year to identify the actual day.
|
||||
|
||||
This method is necessary because the SYSTEMTIME structure
|
||||
refers to a day by its
|
||||
day of the week and week of the month (e.g. 4th saturday in March).
|
||||
|
||||
>>> SATURDAY = 6
|
||||
>>> MARCH = 3
|
||||
>>> st = SYSTEMTIME(2000, MARCH, SATURDAY, 4, 0, 0, 0, 0)
|
||||
|
||||
# according to my calendar, the 4th Saturday in March in 2009 was the 28th
|
||||
>>> expected_date = datetime.datetime(2009, 3, 28)
|
||||
>>> Info._locate_day(2009, st) == expected_date
|
||||
True
|
||||
"""
|
||||
# MS stores Sunday as 0, Python datetime stores Monday as zero
|
||||
target_weekday = (cutoff.day_of_week + 6) % 7
|
||||
# For SYSTEMTIMEs relating to time zone inforamtion, cutoff.day
|
||||
# is the week of the month
|
||||
week_of_month = cutoff.day
|
||||
# so the following is the first day of that week
|
||||
day = (week_of_month - 1) * 7 + 1
|
||||
result = datetime.datetime(
|
||||
year, cutoff.month, day,
|
||||
cutoff.hour, cutoff.minute, cutoff.second, cutoff.millisecond)
|
||||
# now the result is the correct week, but not necessarily
|
||||
# the correct day of the week
|
||||
days_to_go = (target_weekday - result.weekday()) % 7
|
||||
result += datetime.timedelta(days_to_go)
|
||||
# if we selected a day in the month following the target month,
|
||||
# move back a week or two.
|
||||
# This is necessary because Microsoft defines the fifth week in a month
|
||||
# to be the last week in a month and adding the time delta might have
|
||||
# pushed the result into the next month.
|
||||
while result.month == cutoff.month + 1:
|
||||
result -= datetime.timedelta(weeks=1)
|
||||
return result
|
9
libs/win/jaraco/windows/ui.py
Normal file
9
libs/win/jaraco/windows/ui.py
Normal file
|
@ -0,0 +1,9 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import ctypes
|
||||
from jaraco.windows.util import ensure_unicode
|
||||
|
||||
|
||||
def MessageBox(text, caption=None, handle=None, type=None):
|
||||
text, caption = map(ensure_unicode, (text, caption))
|
||||
ctypes.windll.user32.MessageBoxW(handle, text, caption, type)
|
16
libs/win/jaraco/windows/user.py
Normal file
16
libs/win/jaraco/windows/user.py
Normal file
|
@ -0,0 +1,16 @@
|
|||
import ctypes
|
||||
from .api import errors
|
||||
from .api.user import GetUserName
|
||||
from .error import WindowsError, handle_nonzero_success
|
||||
|
||||
|
||||
def get_user_name():
|
||||
size = ctypes.wintypes.DWORD()
|
||||
try:
|
||||
handle_nonzero_success(GetUserName(None, size))
|
||||
except WindowsError as e:
|
||||
if e.code != errors.ERROR_INSUFFICIENT_BUFFER:
|
||||
raise
|
||||
buffer = ctypes.create_unicode_buffer(size.value)
|
||||
handle_nonzero_success(GetUserName(buffer, size))
|
||||
return buffer.value
|
20
libs/win/jaraco/windows/util.py
Normal file
20
libs/win/jaraco/windows/util.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import ctypes
|
||||
|
||||
|
||||
def ensure_unicode(param):
|
||||
try:
|
||||
param = ctypes.create_unicode_buffer(param)
|
||||
except TypeError:
|
||||
pass # just return the param as is
|
||||
return param
|
||||
|
||||
|
||||
class Extended(object):
|
||||
"Used to add extended capability to structures"
|
||||
def __eq__(self, other):
|
||||
return memoryview(self) == memoryview(other)
|
||||
|
||||
def __ne__(self, other):
|
||||
return memoryview(self) != memoryview(other)
|
17
libs/win/jaraco/windows/vpn.py
Normal file
17
libs/win/jaraco/windows/vpn.py
Normal file
|
@ -0,0 +1,17 @@
|
|||
import os
|
||||
from path import Path
|
||||
|
||||
|
||||
def install_pptp(name, param_lines):
|
||||
"""
|
||||
"""
|
||||
# or consider using the API:
|
||||
# http://msdn.microsoft.com/en-us/library/aa446739%28v=VS.85%29.aspx
|
||||
pbk_path = (
|
||||
Path(os.environ['PROGRAMDATA'])
|
||||
/ 'Microsoft' / 'Network' / 'Connections' / 'pbk' / 'rasphone.pbk')
|
||||
pbk_path.dirname().makedirs_p()
|
||||
with open(pbk_path, 'a') as pbk:
|
||||
pbk.write('[{name}]\n'.format(name=name))
|
||||
pbk.writelines(param_lines)
|
||||
pbk.write('\n')
|
100
libs/win/jaraco/windows/xmouse.py
Normal file
100
libs/win/jaraco/windows/xmouse.py
Normal file
|
@ -0,0 +1,100 @@
|
|||
#!python
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import ctypes
|
||||
from jaraco.windows.error import handle_nonzero_success
|
||||
from jaraco.windows.api import system
|
||||
from jaraco.ui.cmdline import Command
|
||||
|
||||
|
||||
def set(value):
|
||||
result = system.SystemParametersInfo(
|
||||
system.SPI_SETACTIVEWINDOWTRACKING,
|
||||
0,
|
||||
ctypes.cast(value, ctypes.c_void_p),
|
||||
0,
|
||||
)
|
||||
handle_nonzero_success(result)
|
||||
|
||||
|
||||
def get():
|
||||
value = ctypes.wintypes.BOOL()
|
||||
result = system.SystemParametersInfo(
|
||||
system.SPI_GETACTIVEWINDOWTRACKING,
|
||||
0,
|
||||
ctypes.byref(value),
|
||||
0,
|
||||
)
|
||||
handle_nonzero_success(result)
|
||||
return bool(value)
|
||||
|
||||
|
||||
def set_delay(milliseconds):
|
||||
result = system.SystemParametersInfo(
|
||||
system.SPI_SETACTIVEWNDTRKTIMEOUT,
|
||||
0,
|
||||
ctypes.cast(milliseconds, ctypes.c_void_p),
|
||||
0,
|
||||
)
|
||||
handle_nonzero_success(result)
|
||||
|
||||
|
||||
def get_delay():
|
||||
value = ctypes.wintypes.DWORD()
|
||||
result = system.SystemParametersInfo(
|
||||
system.SPI_GETACTIVEWNDTRKTIMEOUT,
|
||||
0,
|
||||
ctypes.byref(value),
|
||||
0,
|
||||
)
|
||||
handle_nonzero_success(result)
|
||||
return int(value.value)
|
||||
|
||||
|
||||
class DelayParam(Command):
|
||||
@staticmethod
|
||||
def add_arguments(parser):
|
||||
parser.add_argument(
|
||||
'-d', '--delay', type=int,
|
||||
help="Delay in milliseconds for active window tracking"
|
||||
)
|
||||
|
||||
|
||||
class Show(Command):
|
||||
@classmethod
|
||||
def run(cls, args):
|
||||
msg = "xmouse: {enabled} (delay {delay}ms)".format(
|
||||
enabled=get(),
|
||||
delay=get_delay(),
|
||||
)
|
||||
print(msg)
|
||||
|
||||
|
||||
class Enable(DelayParam):
|
||||
@classmethod
|
||||
def run(cls, args):
|
||||
print("enabling xmouse")
|
||||
set(True)
|
||||
args.delay and set_delay(args.delay)
|
||||
|
||||
|
||||
class Disable(DelayParam):
|
||||
@classmethod
|
||||
def run(cls, args):
|
||||
print("disabling xmouse")
|
||||
set(False)
|
||||
args.delay and set_delay(args.delay)
|
||||
|
||||
|
||||
class Toggle(DelayParam):
|
||||
@classmethod
|
||||
def run(cls, args):
|
||||
value = get()
|
||||
print("xmouse: %s -> %s" % (value, not value))
|
||||
set(not value)
|
||||
args.delay and set_delay(args.delay)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
Command.invoke()
|
Loading…
Add table
Add a link
Reference in a new issue