1 <?php
2 3 4 5 6 7
8 class CodeViewer extends Controller {
9
10 public static $url_handlers = array (
11 '' => 'browse',
12 '$Class' => 'viewClass'
13 );
14
15 16 17 18 19 20
21 static $fsm = array(
22 'start' => array(
23 T_CLASS => array('className','createClass'),
24 T_DOC_COMMENT => array('', 'saveClassComment'),
25 ),
26 'className' => array(
27 T_STRING => array('classSpec', 'setClassName'),
28 ),
29 'classSpec' => array(
30 '{' => 'classBody',
31 ),
32 'classBody' => array(
33 T_FUNCTION => array('methodName','createMethod'),
34 '}' => array('start', 'completeClass'),
35 T_DOC_COMMENT => array('', 'saveMethodComment'),
36 ),
37 'methodName' => array(
38 T_STRING => array('methodSpec', 'setMethodName'),
39 ),
40 'methodSpec' => array(
41 '{' => 'methodBody',
42 ),
43 'methodBody' => array(
44 '{' => array('!push','appendMethodContent'),
45 '}' => array(
46 'hasstack' => array('!pop', 'appendMethodContent'),
47 'nostack' => array('classBody', 'completeMethod'),
48 ),
49 T_VARIABLE => array('variable', 'potentialMethodCall'),
50 T_COMMENT => array('', 'appendMethodComment'),
51 T_DOC_COMMENT => array('', 'appendMethodComment'),
52 '*' => array('', 'appendMethodContent'),
53 ),
54 'variable' => array(
55 T_OBJECT_OPERATOR => array('variableArrow', 'potentialMethodCall'),
56 '*' => array('methodBody', 'appendMethodContent'),
57 ),
58 'variableArrow' => array(
59 T_STRING => array('methodOrProperty', 'potentialMethodCall'),
60 T_WHITESPACE => array('', 'potentialMethodCall'),
61 '*' => array('methodBody', 'appendMethodContent'),
62 ),
63 'methodOrProperty' => array(
64 '(' => array('methodCall', 'potentialMethodCall'),
65 T_WHITESPACE => array('', 'potentialMethodCall'),
66 '*' => array('methodBody', 'appendMethodContent'),
67 ),
68 'methodCall' => array(
69 '(' => array('!push/nestedInMethodCall', 'potentialMethodCall'),
70 ')' => array('methodBody', 'completeMethodCall'),
71 '*' => array('', 'potentialMethodCall'),
72 ),
73 'nestedInMethodCall' => array(
74 '(' => array('!push', 'potentialMethodCall'),
75 ')' => array('!pop', 'potentialMethodCall'),
76 '*' => array('', 'potentialMethodCall'),
77 ),
78 );
79
80 function init() {
81 parent::init();
82
83 if(!Permission::check('ADMIN')) return Security::permissionFailure();
84 ManifestBuilder::load_test_manifest();
85 }
86
87 public function browse() {
88 $classes = ClassInfo::subclassesFor('SapphireTest');
89
90 array_shift($classes);
91 ksort($classes);
92
93 $result ='<h1>View any of the following test classes</h1>';
94 $result .='<ul>';
95 foreach($classes as $class) {
96 $result .="<li><a href=\"{$this->Link($class)}\">$class</a></li>";
97 }
98 $result .='</ul>';
99
100 $result .='<h1>View any of the following other classes</h1>';
101
102 $classes = array_keys(ClassInfo::allClasses());
103 sort($classes);
104
105 $result .='<ul>';
106 foreach($classes as $class) {
107 $result .="<li><a href=\"{$this->Link($class)}\">$class</a></li>";
108 }
109 $result .='</ul>';
110
111 return $this->customise(array (
112 'Content' => $result
113 ))->renderWith('CodeViewer');
114 }
115
116 public function viewClass(SS_HTTPRequest $request) {
117 $class = $request->param('Class');
118
119 if(!class_exists($class)) {
120 throw new Exception('CodeViewer->viewClass(): not passed a valid class to view (does the class exist?)');
121 }
122
123 return $this->customise(array (
124 'Content' => $this->testAnalysis(getClassFile($class))
125 ))->renderWith('CodeViewer');
126 }
127
128 public function Link($action = null) {
129 return Controller::join_links(Director::absoluteBaseURL(), 'dev/viewcode/', $action);
130 }
131
132 protected , ;
133
134 function ($token) {
135 $this->classComment = $this->parseComment($token);
136 }
137 function ($token) {
138 $this->methodComment = $this->parseComment($token);
139 }
140
141 function createClass($token) {
142 $this->currentClass = array(
143 "description" => $this->classComment['pretty'],
144 "heading" => isset($this->classComment['heading']) ? $this->classComment['heading'] : null,
145 );
146 $ths->classComment = null;
147 }
148 function setClassName($token) {
149 $this->currentClass['name'] = $token[1];
150 if(!$this->currentClass['heading']) $this->currentClass['heading'] = $token[1];
151 }
152 function completeClass($token) {
153 $this->classes[] = $this->currentClass;
154 }
155
156 function createMethod($token) {
157 $this->currentMethod = array();
158 $this->currentMethod['content'] = "<pre>";
159 $this->currentMethod['description'] = $this->methodComment['pretty'];
160 $this->currentMethod['heading'] = isset($this->methodComment['heading']) ? $this->methodComment['heading'] : null;
161 $this->methodComment = null;
162
163 }
164 function setMethodName($token) {
165 $this->currentMethod['name'] = $token[1];
166 if(!$this->currentMethod['heading']) $this->currentMethod['heading'] = $token[1];
167 }
168 function ($token) {
169 if(substr($token[1],0,2) == '/*') {
170 $this->closeOffMethodContentPre();
171 $this->currentMethod['content'] .= $this->prettyComment($token) . "<pre>";
172 } else {
173 $this->currentMethod['content'] .= $this->renderToken($token);
174 }
175 }
176
177 function ($token) {
178 $comment = preg_replace('/^\/\*/','',$token[1]);
179 $comment = preg_replace('/\*\/$/','',$comment);
180 $comment = preg_replace('/(^|\n)[\t ]*\* */m',"\n",$comment);
181 $comment = htmlentities($comment);
182 $comment = str_replace("\n\n", "</p><p>", $comment);
183 return "<p>$comment</p>";
184 }
185
186 function ($token) {
187 $parsed = array();
188
189 $comment = preg_replace('/^\/\*/','',$token[1]);
190 $comment = preg_replace('/\*\/$/','',$comment);
191 $comment = preg_replace('/(^|\n)[\t ]*\* */m',"\n",$comment);
192
193 foreach(array('heading','nav') as $var) {
194 if(preg_match('/@' . $var . '\s+([^\n]+)\n/', $comment, $matches)) {
195 $parsed[$var] = $matches[1];
196 $comment = preg_replace('/@' . $var . '\s+([^\n]+)\n/','', $comment);
197 }
198 }
199
200 $parsed['pretty'] = "<p>" . str_replace("\n\n", "</p><p>", htmlentities($comment)). "</p>";
201 return $parsed;
202 }
203
204 protected $isNewLine = true;
205
206 function appendMethodContent($token) {
207 if($this->potentialMethodCall) {
208 $this->currentMethod['content'] .= $this->potentialMethodCall;
209 $this->potentialMethodCall = "";
210 }
211
212 $this->isNewLine = false;
213 $this->currentMethod['content'] .= $this->renderToken($token);
214 }
215 function completeMethod($token) {
216 $this->closeOffMethodContentPre();
217 $this->currentMethod['content'] = str_replace("\n\t\t","\n",$this->currentMethod['content']);
218 $this->currentClass['methods'][] = $this->currentMethod;
219 }
220
221 protected $potentialMethodCall = "";
222 function potentialMethodCall($token) {
223 $this->potentialMethodCall .= $this->renderToken($token);
224 }
225 function completeMethodCall($token) {
226 $this->potentialMethodCall .= $this->renderToken($token);
227 if(strpos($this->potentialMethodCall, '-></span><span class="T_STRING">assert') !== false) {
228 $this->currentMethod['content'] .= "<strong>" . $this->potentialMethodCall . "</strong>";
229 } else {
230 $this->currentMethod['content'] .= $this->potentialMethodCall;
231 }
232 $this->potentialMethodCall = "";
233 }
234
235 236 237 238
239 function closeOffMethodContentPre() {
240 $this->currentMethod['content'] = trim($this->currentMethod['content']);
241 if(substr($this->currentMethod['content'],-5) == '<pre>') $this->currentMethod['content'] = substr($this->currentMethod['content'], 0,-5);
242 else $this->currentMethod['content'] .= '</pre>';
243 }
244
245 246 247
248 function renderToken($token) {
249 $tokenContent = htmlentities(is_array($token) ? $token[1] : $token);
250 $tokenName = is_array($token) ? token_name($token[0]) : 'T_PUNCTUATION';
251
252 switch($tokenName) {
253 case "T_WHITESPACE":
254 if(strpos($tokenContent, "\n") !== false) $this->isNewLine = true;
255 return $tokenContent;
256 default:
257 return "<span class=\"$tokenName\">$tokenContent</span>";
258 }
259 }
260
261 protected $classes = array();
262 protected $currentMethod, $currentClass;
263
264 function testAnalysis($file) {
265 $content = file_get_contents($file);
266 $tokens = token_get_all($content);
267
268
269
270 $state = "start";
271 $stateStack = array();
272
273
274 foreach($tokens as $token) {
275
276 if(is_array($token)) $tokenName = $token[0]; else $tokenName = $token;
277
278
279
280 if(isset(self::$fsm[$state][$tokenName])) $rule = self::$fsm[$state][$tokenName];
281 else if(isset(self::$fsm[$state]['*'])) $rule = self::$fsm[$state]['*'];
282 else $rule = null;
283
284
285 if(is_array($rule) && array_keys($rule) == array('hasstack', 'nostack')) {
286 if($stateStack) $rule = $rule['hasstack'];
287 else $rule = $rule = $rule['nostack'];
288 }
289
290 if(is_array($rule)) {
291 list($destState, $methodName) = $rule;
292 $this->$methodName($token);
293 } else if($rule) {
294 $destState = $rule;
295 } else {
296 $destState = null;
297 }
298
299
300 if(preg_match('/!(push|pop)(\/[a-zA-Z0-9]+)?/', $destState, $parts)) {
301 $action = $parts[1];
302 $argument = isset($parts[2]) ? substr($parts[2],1) : null;
303 $destState = null;
304
305 switch($action) {
306 case "push":
307 $stateStack[] = $state;
308 if($argument) $destState = $argument;
309 break;
310
311 case "pop":
312 if($stateStack) $destState = array_pop($stateStack);
313 else if($argument) $destState = $argument;
314 else user_error("State transition '!pop' was attempted with an empty state-stack and no default option specified.", E_USER_ERROR);
315 }
316 }
317
318 if($destState) $state = $destState;
319 if(!isset(self::$fsm[$state])) user_error("Transition to unrecognised state '$state'", E_USER_ERROR);
320 }
321
322 $subclasses = ClassInfo::subclassesFor('SapphireTest');
323 foreach($this->classes as $classDef) {
324 if(true ||in_array($classDef['name'], $subclasses)) {
325 echo "<h1>$classDef[heading]</h1>";
326 echo "<div style=\"font-weight: bold\">$classDef[description]</div>";
327 if(isset($classDef['methods'])) foreach($classDef['methods'] as $method) {
328 if(true || substr($method['name'],0,4) == 'test') {
329
330 $title = $method['heading'];
331
332 echo "<h2>$title</h2>";
333 echo "<div style=\"font-weight: bold\">$method[description]</div>";
334 echo $method['content'];
335 }
336 }
337 }
338
339 }
340 }
341 }
[Raise a SilverStripe Framework issue/bug](https://github.com/silverstripe/silverstripe-framework/issues/new)
- [Raise a SilverStripe CMS issue/bug](https://github.com/silverstripe/silverstripe-cms/issues/new)
- Please use the
Silverstripe Forums to ask development related questions.
-