-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathdocbrowser.py
More file actions
431 lines (343 loc) · 12.9 KB
/
Copy pathdocbrowser.py
File metadata and controls
431 lines (343 loc) · 12.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
"""
Module docbrowser.py
====================
Classes:
* DocBrowser: represents browser as a Kivy widget.
* DocBrowserApp: represents browser as a stand-alone app.
DocBrowser class
================
Subclass of :class:`~kivy.uix.boxlayout.BoxLayout`.
Represents the widget of a browser.
This widget shows you docstrings of specified Python module/package.
The widget is useful for monitoring the availability of documentation in the
code.
For example, place the following code in any module and run it::
if __name__ == '__main__':
from kivy.app import runTouchApp
from docbrowser import DocBrowser
runTouchApp(DocBrowser(module_name='__main__'))
It will show you documentation of the module, in which you put this example.
You can specify any other module name.
.. note::
Please note that the module you want to see must be installed in the
currently active virtual environment
By default, object inspector displays only the submodules and classes. By
changing appropriate properties you can:
* via :attr:`DocBrowser.functions` - enable/disable display of the module
functions
* via :attr:`DocBrowser.imported` - enable/disable display of the imported
modules
* via :attr:`DocBrowser.methods` - enable/disable display of the class methods
.. warning::
:attr:`DocBrowser.methods` - is an experimental feature. It is strongly
recommended DO NOT enable it if you trying to load documentation of a large
module (such as "kivy").
The following example starts the "DocBrowser" with all available features::
if __name__ == '__main__':
from kivy.app import runTouchApp
from docbrowser import DocBrowser
runTouchApp(
DocBrowser(module_name='__main__', functions=True,
imported=True, methods=True))
.. note::
Please note that if you've changed docstrings you need to restart app to
see the changes.
DocBrowserApp class
===================
The second possible way of usage - as a stand-alone app::
from docbrowser import DocBrowserApp
DocBrowserApp(module_name=__name__).run()
Also app can be started via shell (the module name is specified as a
parameter)::
python docbrowser.py kivy.core
By the way, if you omit this parameter you will see documentation of the module
named "kivy"
"""
from kivy.compat import PY2
from kivy.properties import StringProperty, BooleanProperty, Clock
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.treeview import TreeViewLabel, TreeView
from kivy.lang import Builder
from kivy.app import App
import inspect
from importlib import import_module
from kivy.garden.xpopup import XProgress, XError
__author__ = 'ophermit'
Builder.load_string('''
#:kivy 1.9.1
#:import metrics kivy.metrics
<ObjectInspectorLabel>:
on_touch_down: self.parent.info.text = self.doc if\
self.collide_point(*args[1].pos) and self.doc else\
self.parent.info.text
<DocBrowser>:
orientation: 'vertical'
spacing: 5
padding: [6, 6, 6, 6]
BoxLayout:
orientation: 'horizontal'
spacing: 5
Splitter:
sizable_from: 'right'
min_size: '200dp'
size_hint: (.3, 1)
id: splitter
ScrollView:
ObjectInspector:
id: inspector
info: document
module_name: root.module_name
imported: root.imported
methods: root.methods
functions: root.functions
size_hint_y: None
height: self.minimum_height
hide_root: True
RstDocument:
id: document
size_hint_x: .8
orientation: 'vertical'
source_error: 'ignore'
GridLayout:
size_hint: (1, None)
height: inp_module_name.line_height * 1.7
cols: 2
rows: 1
spacing: [5]
TextInput:
id: inp_module_name
text: root.module_name
hint_text: 'Module name'
multiline: False
on_text_validate: root.load_doc()
focus: True
Button:
id: btn_load
size_hint_x: None
width: metrics.dp(100)
text: 'Load doc'
on_release: root.load_doc()
''')
NO_DOC_STR = 'No documentation found'
class ObjectInspectorLabel(TreeViewLabel):
"""Represents a node in the object inspector. For internal use only.
"""
doc = StringProperty('')
'''Represents docstring.
:attr:`doc` is a :class:`~kivy.properties.StringProperty` and
defaults to ''.
'''
class ObjectInspector(TreeView):
"""Object inspector class. For internal use only.
"""
get_doc = lambda self, x: inspect.getdoc(x) or NO_DOC_STR
'''Predicate to get docstrings
'''
def __init__(self, **kwargs):
# generator object
self.__gen = None
# progressbar popup object
self.__progress = None
# already processed modules (for the circular detection)
self.__submodules = []
# root module node
self.__root_module_node = None
super(ObjectInspector, self).__init__(**kwargs)
def _create_category_node(self, title, parent_node, count):
"""Creates and returns a new category node
:param title: category title
:param parent_node: parent node in the object's inspector tree
:param count: sub-entries count
:return: the newly created category node
"""
return self.add_node(
ObjectInspectorLabel(
text='[ %s (%d) ]' % (title, count), no_selection=True),
parent_node)
def _fill_category_node(self, module, obj_filter, title, parent):
"""Creates and fills new category node. Used as a generator
:param module: module/class object
:param obj_filter: predicate to filter members
:param title: category title
:param parent: parent node in the object's inspector tree
"""
members = inspect.getmembers(module, obj_filter)
if not members:
return
category_node = self._create_category_node(title, parent, len(members))
for entry in members:
node = self.add_node(
ObjectInspectorLabel(text=entry[0],
doc=self.get_doc(entry[1])),
category_node)
# TODO::OPTIMIZE
if self.methods:
if PY2:
obj_filter = inspect.ismethod
else:
# in Python 3.x inspect.ismethod returns []
obj_filter = inspect.isroutine
for method in inspect.getmembers(entry[1], obj_filter):
self.add_node(
ObjectInspectorLabel(
text=method[0], doc=self.get_doc(method[1])),
node)
yield
def _create_module_node(self, module, parent=None):
"""Performs recursive module analysis. Used as a generator.
:param module: object of module/submodule
:param parent: parent node in the object's inspector tree
"""
module_node = self.add_node(
ObjectInspectorLabel(
text='* %s' % module.__name__, doc=self.get_doc(module),
is_open=not parent), parent)
if not parent:
self.__root_module_node = module_node
# Checking for circular import
if module.__name__ in self.__submodules:
module_node.text += ' (circular)'
return
else:
self.__submodules.append(module.__name__)
yield
# Processing the submodules
pred_submodules = lambda x: inspect.ismodule(x)\
and module.__name__ in x.__name__
submodules = inspect.getmembers(module, pred_submodules)
if submodules:
submodule_node = self._create_category_node(
'Submodules', module_node, len(submodules))
for submodule in submodules:
for entry in self._create_module_node(
submodule[1], submodule_node):
yield
# Processing the module's members
categories = [
(True, 'Classes', lambda x: inspect.isclass(x)
and x.__module__ == module.__name__),
(self.functions, 'Functions', lambda x: inspect.isfunction(x)
and x.__module__ == module.__name__),
(self.imported, 'Imported', lambda x:
(inspect.isclass(x) or inspect.isroutine(x))
and x.__module__ != module.__name__)
]
for ctgr in categories:
if ctgr[0]:
for step in self._fill_category_node(
module, ctgr[2], ctgr[1], module_node):
yield
def _fill_tree(self, pdt=None):
"""The main loop of documentation collection.
"""
if self.__progress and self.__progress.is_canceled():
return
try:
next(self.__gen)
Clock.schedule_once(self._fill_tree, .01)
except StopIteration:
self._load_complete()
except Exception as e:
XError(text=str(e))
self._load_complete()
def _load_complete(self):
"""Performing the final steps
"""
self.info.text = self.__root_module_node.doc
if self.__progress:
self.__progress.complete(show_time=0, text='')
self.__progress = None
self.__gen = None
def load_documentation(self, pdt=None):
"""Preparations for collecting documentation.
"""
if self.module_name == '' or self.__gen is not None:
return
# importing specified module
try:
# DON`T REMOVE OR FOUND AND FIX THE ISSUE
# module reloads, but documentation is not changed
"""
if self.module_name in modules.keys()\
and self.module_name != '__main__':
module = reload(modules[self.module_name])
else:
"""
module = import_module(self.module_name)
except ImportError as e:
XError(text=str(e))
return
except Exception as e:
XError(text='Unexpected exception\nReason: %s' % str(e))
return
# clearing the tree
self.__submodules[:] = []
for node in self.iterate_all_nodes():
self.remove_node(node)
# starting the process
self.__progress = XProgress(
buttons=[],
text='Loading documentation...',
title='Module "%s"' % self.module_name)
self.__progress.autoprogress()
self.__gen = self._create_module_node(module)
self._fill_tree()
class DocBrowser(BoxLayout):
"""
DocBrowser widget class. See "docbrowser" module documentation for more
information.
"""
module_name = StringProperty(__name__)
'''Represents module name. Use it to automatically load documentation when
the widget created.
:attr:`module_name` is a :class:`~kivy.properties.StringProperty` and
defaults to __name__.
'''
functions = BooleanProperty(True)
'''Enables/disables display of module functions in the object inspector.
:attr:`functions` is a :class:`~kivy.properties.BooleanProperty` and
defaults to True.
'''
imported = BooleanProperty(False)
'''Enables/disables display of imported modules in the object inspector.
:attr:`module_name` is a :class:`~kivy.properties.BooleanProperty` and
defaults to False.
'''
methods = BooleanProperty(False)
'''Enables/disables display of class methods in the object inspector.
.. warning::
This is an experimental feature. It is not recommended to turn it on if
you trying to load documentation of a large module.
:attr:`methods` is a :class:`~kivy.properties.BooleanProperty` and
defaults to False.
'''
def __init__(self, **kwargs):
super(DocBrowser, self).__init__(**kwargs)
self.load_doc()
def load_doc(self):
"""Callback for the "Load doc" button.
"""
self.ids.inspector.module_name = self.ids.inp_module_name.text
Clock.schedule_once(self.ids.inspector.load_documentation, .1)
class DocBrowserApp(App):
"""
DocBrowser application class. See "docbrowser" module documentation for
more information.
"""
title = StringProperty('Documentation browser')
'''Title of the app.
'''
module_name = StringProperty('')
'''Represents module name. Use it to automatically load documentation when
the app starts.
'''
def build(self):
return DocBrowser(module_name=self.module_name)
if __name__ == '__main__':
import sys
import kivy
kivy.require('1.9.1')
module_name = 'kivy'
if len(sys.argv) > 1:
module_name = sys.argv[1]
DocBrowserApp(module_name=module_name).run()