@@ -26,13 +26,29 @@ def get_controller_code( c=None, app=None ):
26
26
27
27
return code
28
28
29
- def get_exposed_function_code (fname , code = None ):
29
+ exposed_functions = {} # for singleton
30
+
31
+ def generate_exposed_functions_info ():
32
+
33
+
34
+ global exposed_functions
35
+ exposed_functions = getattr (current , 'exposed_functions' , {} )
36
+ current .exposed_functions = exposed_functions
37
+ if not exposed_functions :
38
+ for f in exposed_functions_names ():
39
+ d = exposed_functions [f ] = {}
40
+ d ['code' ] = code = get_exposed_function_code ( f )
41
+ d ['is_task' ] = '_task' in f or f .startswith ('task' ) or "###PLACEHOLDER" in code
42
+ return exposed_functions
43
+
44
+ def get_exposed_function_code (fun_name , code = None ):
45
+
30
46
if code is None :
31
47
code = get_controller_code ()
32
48
33
49
regex_myfun_header = re .compile (
34
50
# r'^def\s+(?P<name>_?[a-zA-Z0-9]\w*)\( *\)\s*:',
35
- r'^def\s+(%s)\( *\)\s*:' % fname ,
51
+ r'^def\s+(%s)\( *\)\s*:' % fun_name ,
36
52
flags = re .M )
37
53
match = regex_myfun_header .search (code )
38
54
myfun_header = match and match .group (0 )
@@ -64,14 +80,14 @@ def lessons_menu(return_plain=False):
64
80
controllers = [ c [:- len ('.py' )] for c in os .listdir ( dirpath ) if c .startswith ('lesson' ) and c .endswith ('.py' )]
65
81
controllers .sort ()
66
82
67
- menu = [ A ( c [len ("lesson" ):].title (), _href = URL (c , 'index' ) ) for c in controllers ]
83
+ menu = [ A ( c [len ("lesson" ):].title (), _href = URL (c , 'index' ) ) for c in controllers ]
68
84
69
85
if return_plain :
70
86
return controllers
71
87
else :
72
88
return UL (menu )
73
89
74
- def exposed_functions ( ):
90
+ def exposed_functions_names ( ):
75
91
76
92
request = current .request
77
93
@@ -83,20 +99,52 @@ def exposed_functions( ):
83
99
items = find_exposed_functions (data )
84
100
85
101
return items
86
-
87
- def menu ( ):
102
+
103
+
104
+
105
+ def menu ( only_category = False ):
88
106
"""gives links to all exposed functions except currently used"""
89
- items = exposed_functions ( )
107
+ fun_names = exposed_functions_names ()
108
+
109
+ generate_exposed_functions_info ()
90
110
request = current .request
91
- return UL ( [
92
- item != request .function
93
- and SPAN ( A ( item , _href = URL (item )), "*" * is_task (item ) )
94
- or item
95
- for item in items
96
- ] )
97
-
98
- def is_task (fname ):
99
- return '_task' in fname or fname .startswith ('task' )
111
+
112
+ htmlized = [
113
+ item if item == request .function
114
+ else SPAN ( A ( item , _href = URL (item )), "*" * exposed_functions [item ]['is_task' ] )
115
+
116
+ for item in fun_names
117
+ if item != 'index'
118
+ ]
119
+
120
+ ctx = {'current_cat' :None }
121
+ def transform_to_tree ():
122
+ "group menu items into categories -- if item name starts with _ it means new category"
123
+ result = []
124
+ cat = []
125
+ cat_name = ""
126
+
127
+ for name , html in zip ( fun_names , htmlized ):
128
+ if name .startswith ('_' ): # means category
129
+ result .append ( TOGGLABLE_CONTENT ( cat_name , UL (cat )) ) #
130
+ cat_name = name .replace ("_" , " " ).title ()
131
+ cat = [ ]
132
+
133
+ if only_category and name == request .function :
134
+ return cat_name # dirty hack to get current category name
135
+
136
+ cat .append ( html )
137
+
138
+ result .append (TOGGLABLE_CONTENT (cat_name , UL (cat ))) # last category
139
+
140
+ return result
141
+
142
+ if only_category :
143
+ return transform_to_tree ()
144
+
145
+ return UL ( transform_to_tree () )
146
+ # return UL( htmlized )
147
+
100
148
101
149
102
150
def TOGGLABLE_CONTENT (name , content ):
@@ -106,11 +154,11 @@ def TOGGLABLE_CONTENT(name, content):
106
154
"""
107
155
108
156
return CAT (
109
- BR (),
157
+
110
158
SPAN ( name , _class = "button" , _onclick = js_toggle , _style = "cursor:hand" ),
111
159
SPAN ( content , _class = "togglable" )
112
160
# SPAN( content, _class="togglable", _style="display:none" )
113
- , BR (), SPAN ("" , _class = "after_togglable" )
161
+ ,SPAN ("" , _class = "after_togglable" )
114
162
)
115
163
116
164
def CODEMIRROR (code , language = "python" , task_key = None ):
@@ -121,11 +169,29 @@ def CODEMIRROR(code, language="python", task_key=None):
121
169
return XML (current .response .render ('codemirror.html' , dict (code = code , language = language , task_key = task_key )))
122
170
123
171
124
-
172
+ import traceback
125
173
def tutor (f ):
126
174
"""smart decorator"""
127
175
def result ():
128
- content = f ()
176
+ try :
177
+ content = f ()
178
+ except Exception as e :
179
+ # content = repr( e )
180
+ tb_str = traceback .format_exc ()
181
+ lines = tb_str .split ("\n " )
182
+ # hide path and tutor decorator info
183
+ for nr , line in enumerate (lines ):
184
+ if 'File "' in line and '/controllers/' in line :
185
+ a , b = line .split ( 'File "' , 1 )
186
+ _ , b = b .split ('/controllers/' , 1 )
187
+ lines [nr ] = a + 'File "' + b
188
+
189
+ tb_str = "\n " .join ( lines [:1 ]+ lines [3 :] ) # hide lines about plugin_introspect
190
+ content = CAT ( "KLAIDA:" , PRE ( tb_str , _style = "color:brown" ) )
191
+
192
+ if 'plain' in current .request .vars :
193
+ return content
194
+
129
195
about = get_module_doc ( get_controller_code () )
130
196
131
197
codes = TOGGLABLE_CONTENT ("[ Kodas ]" , get_task_code (f ))
@@ -135,18 +201,25 @@ def result():
135
201
menu_ = TOGGLABLE_CONTENT ("[ Meniu ]" , menu ())
136
202
137
203
# next menu
138
- items = exposed_functions ( )
204
+ items = exposed_functions_names ( )
139
205
req = current .request
140
206
nr = items .index ( req .function )
141
207
next = items [nr + 1 ] if nr < len (items )- 1 else None
142
208
prev = items [nr - 1 ] if nr > 0 else None
143
209
144
210
a_next = A ("[ Pirmyn ]" , _href = URL (next )) if next != None else ""
145
211
a_prev = A ("[ Atgal ]" , _href = URL (prev )) if prev != None else ""
146
- menu_ = CAT ( BR (), a_prev , a_next , BR (), menu_ )
212
+ navigate_prev_next = CAT ( a_prev , " " , a_next )
213
+ # menu_ = CAT( BR(), a_prev, a_next, BR(), menu_ )
214
+ current_category = menu (only_category = True )
147
215
148
216
149
- return XML (current .response .render ('tutor.html' , dict ( about = about , content = content , codes = codes , menu = menu_ ) ) )
217
+ return XML (current .response .render ('tutor.html' ,
218
+ dict ( about = about , content = content , codes = codes , menu = menu_ ,
219
+ navigate_prev_next = navigate_prev_next ,
220
+ current_category = current_category
221
+ )
222
+ ) )
150
223
# return gluon.template.render(content='...', context=<vars>)
151
224
return result
152
225
0 commit comments