Skip to content

Commit 037d68d

Browse files
[fixed #11] optimized, improved functionality
1 parent 00753b7 commit 037d68d

File tree

7 files changed

+315
-411
lines changed

7 files changed

+315
-411
lines changed

.github/workflows/python-publish-test.yml

Lines changed: 0 additions & 33 deletions
This file was deleted.

Pipfile

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[[source]]
2+
name = "pypi"
3+
url = "https://pypi.org/simple"
4+
verify_ssl = true
5+
6+
[dev-packages]
7+
8+
[packages]
9+
pywin32 = "*"
10+
11+
[requires]
12+
python_version = "3.7"

Pipfile.lock

Lines changed: 37 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyvba/__init__.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
2-
from pyvba.browser import *
3-
from pyvba.export import *
4-
from pyvba.viewer import *
1+
from .viewer import Viewer, FunctionViewer, CollectionViewer
2+
from .browser import Browser, CollectionBrowser
3+
from .export import XMLExport, JSONExport

pyvba/browser.py

Lines changed: 59 additions & 154 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
1-
from pyvba.viewer import Viewer, FunctionViewer, IterableFunctionViewer
1+
from pyvba.viewer import Viewer, FunctionViewer, CollectionViewer
22

3+
# used to skip predetermined objects by exact name
4+
# fixme: remove extra parameters
5+
skip = ['Application', 'Parent', 'Units', 'Parameters']
36

7+
8+
# TODO: fix infinite recursion issue
49
class Browser(Viewer):
5-
def __init__(self, app, **kwargs):
10+
def __init__(self, app, name: str, parent: Viewer = None):
611
"""Create a browser from an application string or win32com object.
712
813
The Browser object used to iterate through and explore COM objects from external
@@ -13,27 +18,44 @@ def __init__(self, app, **kwargs):
1318
app
1419
The application string (e.g. "Excel.Application") or win32com object.
1520
"""
16-
super().__init__(app, **kwargs)
17-
18-
# define filters
19-
self._skip = kwargs.get('skip', ["Application", "Parent"]) # user-defined skips
20-
self._checked = kwargs.get('checked', {}) # checked items
21-
self._max_checks = kwargs.get('max_checks', 1) # maximum allowable instances of an object
22-
21+
super().__init__(app, name, parent)
2322
self._all = {}
2423

2524
def __str__(self):
26-
return super().__str__().replace("Viewer", "Browser")
25+
return super().__str__().replace('Viewer', 'Browser')
2726

2827
def __getattr__(self, item):
2928
if self._all == {}:
3029
self._generate()
3130

32-
try:
33-
obj = self._all[item]
34-
except AttributeError:
35-
obj = getattr(self._com, item)
36-
return obj
31+
obj = super().getattr(item)
32+
return obj if isinstance(obj, FunctionViewer) or not isinstance(obj, Viewer) else self.from_viewer(obj)
33+
34+
@staticmethod
35+
def from_viewer(viewer, parent=None):
36+
"""Turn a Viewer object into a Browser object."""
37+
return CollectionBrowser(viewer) if isinstance(viewer, CollectionViewer) \
38+
else Browser(viewer.com, viewer.name, viewer.parent if parent is None else parent)
39+
40+
@staticmethod
41+
def skip(item: str):
42+
"""Adds a keyword to the skip list."""
43+
global skip
44+
if item not in skip:
45+
skip.append(item)
46+
47+
@staticmethod
48+
def rm_skip(item: str):
49+
"""Remove a keyword from the skip list."""
50+
global skip
51+
if item in skip:
52+
skip.remove(item)
53+
54+
@staticmethod
55+
def clr_skip():
56+
"""Resets the skip list to its original state."""
57+
global skip
58+
skip = ['Application', 'Parent']
3759

3860
@property
3961
def all(self) -> dict:
@@ -43,87 +65,25 @@ def all(self) -> dict:
4365
return self._all
4466

4567
def _generate(self):
46-
"""Iterates through all instances of COM objects when called upon.
47-
48-
See Also
49-
--------
50-
Viewer.__getattr___
51-
"""
68+
"""Iterates through all objects when called upon."""
69+
global skip
5270

53-
for attr in self:
54-
# skip if checked or user opts to skip it
55-
if attr in self._skip or self._checked.get(attr, self._max_checks) <= 0:
71+
for name in self._objects + [i.name for i in self._methods]:
72+
if name in skip:
5673
continue
5774

58-
# attempt to collect and observe the attribute
5975
try:
60-
obj = getattr(self._com, attr)
61-
62-
# sort by type
63-
if '<bound method' in repr(obj):
64-
# check for Item array
65-
if "Item" == attr:
66-
# create the IterableFunctionBrowser
67-
# TODO infinite item issue
68-
try:
69-
count = getattr(self._com, 'Count')
70-
self._all[attr] = IterableFunctionBrowser(
71-
obj, attr, count,
72-
parent_name=self._name,
73-
parent=self,
74-
skip=self._skip,
75-
checked=self._checked,
76-
max_checks=self._max_checks
77-
)
78-
except Exception:
79-
self._all[attr] = FunctionViewer(obj, attr)
80-
else:
81-
self._all[attr] = FunctionViewer(obj, attr)
82-
elif 'win32com' in repr(obj) or 'COMObject' in repr(obj):
83-
self._checked[attr] = self._checked.get(attr, self._max_checks) - 1
84-
self._all[attr] = Browser(
85-
obj,
86-
parent=self,
87-
name=attr,
88-
skip=self._skip,
89-
checked=self._checked,
90-
max_checks=self._max_checks
91-
)
76+
obj = super().getattr(name)
77+
78+
if isinstance(obj, Viewer):
79+
self._all[name] = self.from_viewer(obj, self)
9280
else:
93-
self._all[attr] = obj
81+
self._all[name] = obj
9482
except BaseException as e:
95-
self._errors[attr] = e.args
96-
self._all[attr] = e
83+
self._errors[name] = e.args
9784
continue
9885

99-
def print_browser(self, **kwargs):
100-
"""Prints out `all` in a readable way."""
101-
item = kwargs.get('item', self)
102-
tabs = kwargs.get('tabs', 0)
103-
name = ''
104-
105-
# attach a name if not self describing
106-
if 'class' not in str(item):
107-
name = kwargs.get('name') + ': '
108-
109-
print(" " * tabs + name + str(item))
110-
111-
# iterate through the corresponding browser
112-
if isinstance(item, (Browser, IterableFunctionBrowser)):
113-
for i in list(item.all.keys()):
114-
self.print_browser(item=item.all[i], name=i, tabs=(tabs + 1))
115-
116-
def skip(self, item: str):
117-
"""Adds a keyword to the skip list."""
118-
if item not in self._skip:
119-
self._skip.append(item)
120-
121-
def rm_skip(self, item: str):
122-
"""Remove a keyword from the skip list."""
123-
if item in self._skip:
124-
self._skip.remove(item)
125-
126-
def search(self, name: str, exact: bool = False, **kwargs):
86+
def search(self, name: str, exact: bool = False):
12787
"""Return a dictionary in format {path: item} matching the name.
12888
12989
Search for all instances of a method or object containing a name.
@@ -140,28 +100,7 @@ def search(self, name: str, exact: bool = False, **kwargs):
140100
dict
141101
The results of the search in format {path: item}.
142102
"""
143-
144-
item = kwargs.get('item', self)
145-
paths = []
146-
path = kwargs.get('path', self._name)
147-
148-
# add to list if found
149-
if type(item) == FunctionViewer and name in item.name:
150-
if not exact or (exact and name == item.name):
151-
paths.append(path + '/' + item.name)
152-
153-
# search
154-
if isinstance(item, (Browser, IterableFunctionBrowser)):
155-
for i in item.all:
156-
if name in i:
157-
if not exact or (exact and i == name):
158-
paths.append(path + '/' + i)
159-
160-
value = item.all[i]
161-
if isinstance(value, (Browser, IterableFunctionBrowser)):
162-
paths += self.search(name, exact, item=value, path=(path + '/' + i))
163-
164-
return paths
103+
...
165104

166105
def goto(self, path: str):
167106
"""Retrieve an object at a given location.
@@ -176,56 +115,22 @@ def goto(self, path: str):
176115
`goto('Bodies/Item/1/HybridShapes/GetItem')` yields the 'GetItem' function.
177116
178117
"""
179-
item = self
180-
path = path.split('/')
181-
182-
for loc in path[1:]:
183-
if isinstance(item, IterableFunctionBrowser):
184-
item = item(int(loc))
185-
else:
186-
item = getattr(item, loc)
187-
return item
188-
189-
def reset(self):
190-
"""Clear the `all` property and the checked list."""
191-
self._checked = {}
192-
self._all = {}
193-
194-
def reset_all(self):
195-
"""Clear the `all` property and all empties the skip list."""
196-
self._skip = []
197-
self.reset()
118+
...
198119

199120
def regen(self):
200121
"""Regenerate the `all` property."""
201-
self.reset()
122+
self._all = {}
202123
self._generate()
203124

204125

205-
class IterableFunctionBrowser(IterableFunctionViewer):
206-
def __init__(self, func, name, count, **kwargs):
207-
"""Create a browser for an iterable function to view its components."""
208-
super().__init__(func, name, count, **kwargs)
209-
210-
self._all = {
211-
str(i): Browser(func(i), name=(kwargs.get('parent_name')), **kwargs)
212-
for i in range(1, count + 1)
213-
}
126+
class CollectionBrowser(Browser, CollectionViewer):
127+
def __init__(self, obj: CollectionViewer):
128+
super().__init__(obj.com, obj.name, obj.parent)
129+
self._items = [self.from_viewer(i) for i in self._items]
214130

215131
def __str__(self):
216-
return super().__str__().replace('FunctionViewer', "FunctionBrowser")
132+
return "<class 'CollectionBrowser'>: " + self._name
217133

218-
def __getattr__(self, item):
219-
if self._all == {}:
220-
self._generate()
221-
222-
try:
223-
obj = self._all[item]
224-
except AttributeError:
225-
obj = getattr(self._com, item)
226-
return obj
227-
228-
@property
229-
def all(self):
230-
"""Return a dict of objects in the form `{name: item}`."""
231-
return self._all
134+
def _generate(self):
135+
super()._generate()
136+
self._all['Item'] = self._items

0 commit comments

Comments
 (0)