1 <?php
2 3 4 5 6
7 class TestViewer extends Controller {
8 9 10 11 12 13
14 static $fsm = array(
15 'start' => array(
16 T_CLASS => array('className','createClass'),
17 ),
18 'className' => array(
19 T_STRING => array('classSpec', 'setClassName'),
20 ),
21 'classSpec' => array(
22 '{' => 'classBody',
23 ),
24 'classBody' => array(
25 T_FUNCTION => array('methodName','createMethod'),
26 '}' => array('start', 'completeClass'),
27 ),
28 'methodName' => array(
29 T_STRING => array('methodSpec', 'setMethodName'),
30 ),
31 'methodSpec' => array(
32 '{' => 'methodBody',
33 ),
34 'methodBody' => array(
35 '{' => array('!push','appendMethodContent'),
36 '}' => array(
37 'hasstack' => array('!pop', 'appendMethodContent'),
38 'nostack' => array('classBody', 'completeMethod'),
39 ),
40 T_VARIABLE => array('variable', 'potentialMethodCall'),
41 T_COMMENT => array('', 'appendMethodComment'),
42 '*' => array('', 'appendMethodContent'),
43 ),
44 'variable' => array(
45 T_OBJECT_OPERATOR => array('variableArrow', 'potentialMethodCall'),
46 '*' => array('methodBody', 'appendMethodContent'),
47 ),
48 'variableArrow' => array(
49 T_STRING => array('methodOrProperty', 'potentialMethodCall'),
50 T_WHITESPACE => array('', 'potentialMethodCall'),
51 '*' => array('methodBody', 'appendMethodContent'),
52 ),
53 'methodOrProperty' => array(
54 '(' => array('methodCall', 'potentialMethodCall'),
55 T_WHITESPACE => array('', 'potentialMethodCall'),
56 '*' => array('methodBody', 'appendMethodContent'),
57 ),
58 'methodCall' => array(
59 '(' => array('!push/nestedInMethodCall', 'potentialMethodCall'),
60 ')' => array('methodBody', 'completeMethodCall'),
61 '*' => array('', 'potentialMethodCall'),
62 ),
63 'nestedInMethodCall' => array(
64 '(' => array('!push', 'potentialMethodCall'),
65 ')' => array('!pop', 'potentialMethodCall'),
66 '*' => array('', 'potentialMethodCall'),
67 ),
68 );
69
70 function init() {
71 parent::init();
72
73 $canAccess = (Director::isDev() || Director::is_cli() || Permission::check("ADMIN"));
74 if(!$canAccess) return Security::permissionFailure($this);
75 }
76
77 function createClass($token) {
78 $this->currentClass = array();
79 }
80 function setClassName($token) {
81 $this->currentClass['name'] = $token[1];
82 }
83 function completeClass($token) {
84 $this->classes[] = $this->currentClass;
85 }
86
87 function createMethod($token) {
88 $this->currentMethod = array();
89 $this->currentMethod['content'] = "<pre>";
90 }
91 function setMethodName($token) {
92 $this->currentMethod['name'] = $token[1];
93 }
94 function ($token) {
95 if(substr($token[1],0,2) == '/*') {
96 $comment = preg_replace('/^\/\*/','',$token[1]);
97 $comment = preg_replace('/\*\/$/','',$comment);
98 $comment = preg_replace('/\n[\t ]*\* */m',"\n",$comment);
99
100 $this->closeOffMethodContentPre();
101 $this->currentMethod['content'] .= "<p>$comment</p><pre>";
102 } else {
103 $this->currentMethod['content'] .= $this->renderToken($token);
104 }
105
106 }
107 function appendMethodContent($token) {
108 if($this->potentialMethodCall) {
109 $this->currentMethod['content'] .= $this->potentialMethodCall;
110 $this->potentialMethodCall = "";
111 }
112 $this->currentMethod['content'] .= $this->renderToken($token);
113 }
114 function completeMethod($token) {
115 $this->closeOffMethodContentPre();
116 $this->currentMethod['content'] = str_replace("\n\t\t","\n",$this->currentMethod['content']);
117 $this->currentClass['methods'][] = $this->currentMethod;
118 }
119
120 protected $potentialMethodCall = "";
121 function potentialMethodCall($token) {
122 $this->potentialMethodCall .= $this->renderToken($token);
123 }
124 function completeMethodCall($token) {
125 $this->potentialMethodCall .= $this->renderToken($token);
126 if(strpos($this->potentialMethodCall, '-></span><span class="T_STRING">assert') !== false) {
127 $this->currentMethod['content'] .= "<strong>" . $this->potentialMethodCall . "</strong>";
128 } else {
129 $this->currentMethod['content'] .= $this->potentialMethodCall;
130 }
131 $this->potentialMethodCall = "";
132 }
133
134 135 136 137
138 function closeOffMethodContentPre() {
139 $this->currentMethod['content'] = trim($this->currentMethod['content']);
140 if(substr($this->currentMethod['content'],-5) == '<pre>') $this->currentMethod['content'] = substr($this->currentMethod['content'], 0,-5);
141 else $this->currentMethod['content'] .= '</pre>';
142 }
143
144 145 146
147 function renderToken($token) {
148 $tokenContent = htmlentities(is_array($token) ? $token[1] : $token);
149 $tokenName = is_array($token) ? token_name($token[0]) : 'T_PUNCTUATION';
150
151 switch($tokenName) {
152 case "T_WHITESPACE":
153 return $tokenContent;
154 default:
155 return "<span class=\"$tokenName\">$tokenContent</span>";
156 }
157 }
158
159 protected $classes = array();
160 protected $currentMethod, $currentClass;
161
162 function Content() {
163 $className = $this->urlParams['ID'];
164 if($className && ClassInfo::exists($className)) {
165 return $this->testAnalysis(getClassFile($className));
166 } else {
167 $result = "<h1>View any of the following test classes</h1>";
168 $classes = ClassInfo::subclassesFor('SapphireTest');
169 ksort($classes);
170 foreach($classes as $className) {
171 if($className == 'SapphireTest') continue;
172 $result .= "<li><a href=\"TestViewer/show/$className\">$className</a></li>";
173 }
174 return $result;
175 }
176 }
177
178 function testAnalysis($file) {
179 $content = file_get_contents($file);
180 $tokens = token_get_all($content);
181
182
183
184 $state = "start";
185 $stateStack = array();
186
187
188 foreach($tokens as $token) {
189
190 if(is_array($token)) $tokenName = $token[0]; else $tokenName = $token;
191
192
193
194 if(isset(self::$fsm[$state][$tokenName])) $rule = self::$fsm[$state][$tokenName];
195 else if(isset(self::$fsm[$state]['*'])) $rule = self::$fsm[$state]['*'];
196 else $rule = null;
197
198
199 if(is_array($rule) && array_keys($rule) == array('hasstack', 'nostack')) {
200 if($stateStack) $rule = $rule['hasstack'];
201 else $rule = $rule = $rule['nostack'];
202 }
203
204 if(is_array($rule)) {
205 list($destState, $methodName) = $rule;
206 $this->$methodName($token);
207 } else if($rule) {
208 $destState = $rule;
209 } else {
210 $destState = null;
211 }
212
213
214 if(preg_match('/!(push|pop)(\/[a-zA-Z0-9]+)?/', $destState, $parts)) {
215 $action = $parts[1];
216 $argument = isset($parts[2]) ? substr($parts[2],1) : null;
217 $destState = null;
218
219 switch($action) {
220 case "push":
221 $stateStack[] = $state;
222 if($argument) $destState = $argument;
223 break;
224
225 case "pop":
226 if($stateStack) $destState = array_pop($stateStack);
227 else if($argument) $destState = $argument;
228 else user_error("State transition '!pop' was attempted with an empty state-stack and no default option specified.", E_USER_ERROR);
229 }
230 }
231
232 if($destState) $state = $destState;
233 if(!isset(self::$fsm[$state])) user_error("Transition to unrecognised state '$state'", E_USER_ERROR);
234 }
235
236 $subclasses = ClassInfo::subclassesFor('SapphireTest');
237 foreach($this->classes as $classDef) {
238 if(in_array($classDef['name'], $subclasses)) {
239 echo "<h1>$classDef[name]</h1>";
240 if($classDef['methods']) foreach($classDef['methods'] as $method) {
241 if(substr($method['name'],0,4) == 'test') {
242
243 $title = $method['name'];
244
245 echo "<h2>$title</h2>";
246 echo $method['content'];
247 }
248 }
249 }
250
251 }
252 }
253 }
[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.
-