Webylon 3.2 API Docs
  • Package
  • Class
  • Tree
  • Deprecated
  • Download
Version: current
  • 3.2
  • 3.1

Packages

  • 1c
    • exchange
      • catalog
  • auth
  • Booking
  • building
    • company
  • cart
    • shipping
    • steppedcheckout
  • Catalog
    • monument
  • cms
    • assets
    • batchaction
    • batchactions
    • bulkloading
    • comments
    • content
    • core
    • export
    • newsletter
    • publishers
    • reports
    • security
    • tasks
  • Dashboard
  • DataObjectManager
  • event
  • faq
  • forms
    • actions
    • core
    • fields-basic
    • fields-dataless
    • fields-datetime
    • fields-files
    • fields-formatted
    • fields-formattedinput
    • fields-relational
    • fields-structural
    • transformations
    • validators
  • googlesitemaps
  • guestbook
  • installer
  • newsletter
  • None
  • photo
    • gallery
  • PHP
  • polls
  • recaptcha
  • sapphire
    • api
    • bulkloading
    • control
    • core
    • cron
    • dev
    • email
    • fields-formattedinput
    • filesystem
    • formatters
    • forms
    • i18n
    • integration
    • misc
    • model
    • parsers
    • search
    • security
    • tasks
    • testing
    • tools
    • validation
    • view
    • widgets
  • seo
    • open
      • graph
  • sfDateTimePlugin
  • spamprotection
  • stealth
    • captha
  • subsites
  • userform
    • pagetypes
  • userforms
  • webylon
  • widgets

Classes

  • CodeViewer
  • ModelViewer
  • ModelViewer_Field
  • ModelViewer_Model
  • ModelViewer_Module
  • ModelViewer_Relation
  1 <?php
  2 /**
  3  * Allows human reading of a test in a format suitable for agile documentation
  4  * 
  5  * @package sapphire
  6  * @subpackage tools
  7  */
  8 class CodeViewer extends Controller {
  9     
 10     public static $url_handlers = array (
 11         ''       => 'browse',
 12         '$Class' => 'viewClass'
 13     );
 14     
 15     /**
 16      * Define a simple finite state machine.
 17      * Top keys are the state names.  'start' is the first state, and 'die' is the error state.
 18      * Inner keys are token names/codes.  The values are either a string, new state, or an array(new state, handler method).
 19      * The handler method will be passed the PHP token as an argument, and is expected to populate a property of the object.
 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 $classComment, $methodComment;
133 
134     function saveClassComment($token) {
135         $this->classComment = $this->parseComment($token);
136     }
137     function saveMethodComment($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 appendMethodComment($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 prettyComment($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 parseComment($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         //if($this->isNewLine && isset($token[2])) $this->currentMethod['content'] .= $token[2] . ": ";
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, '-&gt;</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      * Finish the "pre" block in method content.
237      * Will remove whitespace and empty "pre" blocks
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      * Render the given token as HTML
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         // Execute a finite-state-machine with a built-in state stack
269         // This FSM+stack gives us enough expressive power for simple PHP parsing
270         $state = "start";
271         $stateStack = array();
272         
273         //echo "<li>state $state";
274         foreach($tokens as $token) {
275             // Get token name - some tokens are arrays, some arent'
276             if(is_array($token)) $tokenName = $token[0]; else $tokenName = $token;
277             //echo "<li>token '$tokenName'";
278             
279             // Find the rule for that token in the current state
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             // Check to see if we have specified multiple rules depending on whether the stack is populated 
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             //echo "<li>->state $destState";
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                         //$title = ucfirst(strtolower(preg_replace('/([a-z])([A-Z])/', '$1 $2', substr($method['name'], 4))));
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. -
Webylon 3.2 API Docs API documentation generated by ApiGen 2.8.0