Source for file Default.php
Documentation is available at Default.php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
* This file is part of the PEAR Testing_DocTest package.
* LICENSE: This source file is subject to the MIT license that is available
* through the world-wide-web at the following URI:
* http://opensource.org/licenses/mit-license.php
* @package Testing_DocTest
* @author David JEAN LOUIS <izimobil@gmail.com>
* @copyright 2008 David JEAN LOUIS
* @license http://opensource.org/licenses/mit-license.php MIT License
* @link http://pear.php.net/package/Testing_DocTest
* @since File available since release 0.1.0
require_once 'Testing/DocTest/ParserInterface.php';
require_once 'Testing/DocTest/TestSuite.php';
require_once 'Testing/DocTest/TestCase.php';
* DocTest Parser default class.
* Important note: this class will be refactored soon so do not rely on it yet
* if you want to subclass or customize Testing_DocTest.
* @package Testing_DocTest
* @author David JEAN LOUIS <izimobil@gmail.com>
* @copyright 2008 David JEAN LOUIS
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version Release: 0.3.1
* @link http://pear.php.net/package/Testing_DocTest
* @since Class available since release 0.1.0
// doctest syntax prefix {{{
* Doctest syntax prefix default is a standard php inline comment: '//'
const SYNTAX_PREFIX = '//';
// Keywords constants {{{
* Keyword for the name of the external doctest file
const KW_DOCTEST_FILE = 'test-file';
* Keyword for the name of the doctest
const KW_DOCTEST_NAME = 'doctest';
* Keyword for the doctest flags
const KW_DOCTEST_FLAGS = 'flags';
* Keyword for the skip condition
const KW_DOCTEST_SKIP_IF = 'skip-if';
* Keyword for the ini settings
const KW_DOCTEST_INI_SET = 'ini-set';
* Keyword for the tmpl code settings
const KW_DOCTEST_TMPL_CODE = 'tmpl-code';
* Keyword for the doctest expected result
const KW_DOCTEST_EXPECTS = 'expects';
* Keyword for the doctest expected file
const KW_DOCTEST_EXPECTS_FILE = 'expects-file';
* Keyword for the clean part
const KW_DOCTEST_CLEAN = 'clean';
* Keyword for the setup part
const KW_DOCTEST_SETUP = 'setup';
* State after parsing a doctest line.
* State after parsing flags.
* State after parsing a skip condition line.
* State after parsing a ini-set line.
* State after parsing expects line.
* State after parsing expects-file line.
const STATE_EXPECTS_FILE = 6;
* State after parsing code line.
* State after parsing clean line.
* State after parsing setup line.
* State after parsing tmpl code.
const STATE_TMPL_CODE = 10;
* Current state of the parser, null or one of the STATE_* constants.
* Testing_DocTest_TestCase instance.
private $_testCase = null;
* set command line options
* @param array $options an array of command line options
$this->_shellOptions = $options;
* Parse the files passed and return an array of Testing_DocTest_TestSuite
* @param array $files an array of file pathes
public function parse (array $files)
$kw = preg_quote (self ::KW_DOCTEST_NAME , '/') . '|'
. preg_quote (self ::KW_DOCTEST_FLAGS , '/') . '|'
. preg_quote (self ::KW_DOCTEST_SKIP_IF , '/') . '|'
. preg_quote (self ::KW_DOCTEST_INI_SET , '/') . '|'
. preg_quote (self ::KW_DOCTEST_SETUP , '/') . '|'
. preg_quote (self ::KW_DOCTEST_TMPL_CODE , '/') . '|'
. preg_quote (self ::KW_DOCTEST_CLEAN , '/') . '|'
. preg_quote (self ::KW_DOCTEST_EXPECTS , '/') . '|'
. preg_quote (self ::KW_DOCTEST_EXPECTS_FILE , '/');
foreach ($files as $file) {
$testCaseArray = $this->_parseFile ($file);
foreach ($testCaseArray as $testCaseData) {
// split raw code into lines
$docblocs = $this->_extractCodeBlocs ($testCaseData['docComment']);
if (!empty ($docblocs) && false == $suite) {
$suite = new Testing_DocTest_TestSuite ();
foreach ($docblocs as $docbloc) {
$this->_testCase ->_shellOptions = $this->_shellOptions;
$this->_testCase ->file = $file;
$this->_testCase ->level = $testCaseData['level'];
$this->_testCase ->name = $testCaseData['name'];
// split string into an array of lines
foreach ($lines as $i=> $l) {
// remove spaces and * at the beginning
if (preg_match(" /^\s*$p\s?($kw):\s*(.*)$/" , $l, $m)) {
//First doctest line number
$this->_testCase ->lineNumber = $testCaseData['line'];
case self ::KW_DOCTEST_NAME:
$this->_handleDoctestLine ($m[2 ]);
case self ::KW_DOCTEST_FLAGS:
$this->_handleFlagsLine ($m[2 ]);
case self ::KW_DOCTEST_SKIP_IF:
$this->_handleFlagsLine ($m[2 ]);
case self ::KW_DOCTEST_INI_SET:
$this->_handleIniSetLine ($m[2 ]);
case self ::KW_DOCTEST_EXPECTS:
$this->_handleExpectsLine ($m[2 ]);
case self ::KW_DOCTEST_EXPECTS_FILE:
$this->_handleExpectsFileLine ($m[2 ]);
case self ::KW_DOCTEST_CLEAN:
$this->_handleCleanLine ($m[2 ]);
case self ::KW_DOCTEST_SETUP:
$this->_handleSetupLine ($m[2 ]);
case self ::KW_DOCTEST_TMPL_CODE:
$this->_handleTmplCode ($m[2 ]);
} else if (preg_match('/^\s*'. $p. '\s?(.*)$/', $l, $m)) {
$this->_handleLineContinuation ($m[1 ]);
$this->_handleCodeLine ($l);
$this->_testCase ->parsingError = $e->getMessage ();
$this->_testCase ->expectedValue
= substr($this->_testCase ->expectedValue , 0 , -1 );
$suite->addTestCase ($this->_testCase );
* Parse the file $file and return an array of Testing_DocTest_TestCase
* @param string $file path to the file to parse.
private function _parseFile ($file)
$tokens = $this->_tokenize ($file);
while (false !== ($item = each($tokens))) {
// memoize curly level in order to detect if we are inside a class
if ($item['value'] == '{') {
} else if ($item['value'] == '}'
&& -- $curlyLevel == $curlyOpen
// curly is the close curly of current class
if ($item['value'] == '"') {
$insideQuote = !$insideQuote;
list ($id, $token, $line) = $item['value'];
// skip all tokens but doc comments
if ($id !== T_DOC_COMMENT ) {
$ids = array (T_CLASS , T_FUNCTION , T_DOC_COMMENT );
$next = $this->_findNextToken ($ids, $tokens);
if (false === $next && !empty($return)) {
// build Testing_DocTest_TestCase instance
$ret['docComment'] = $token;
if (false === $next || T_DOC_COMMENT === $next[0 ]) {
$ret['level'] = 'file level';
$nToken = $this->_findNextToken (T_STRING , $tokens);
if ($next[0 ] === T_CLASS ) {
$curlyOpen = $curlyLevel;
$ret['name'] = $nToken[1 ];
} else if ($insideClass) {
$ret['name'] = $className . '::' . $nToken[1 ];
$ret['level'] = 'method';
$ret['name'] = $nToken[1 ];
$ret['level'] = 'function';
* Tokenize the file $file into an array of tokens using the builtin php
* tokenizer extension. Before tokenizing the method check that the file
* contains at least a doctest.
* @param string $file the file to parse.
* @return array array of tokens
private function _tokenize ($file)
// speed improvement, don't bother tokenizing file if it does not
if (false === strstr($data, self ::KW_DOCTEST_EXPECTS )) {
* Find the next token matching the id $id and return it or return false if
* no matching token is found.
* @param mixed $id id or array of ids the token must match
* @param array &$tokens tokens array passed by reference
* @return array array of tokens
private function _findNextToken ($id, &$tokens)
while ($next !== false ) {
if (is_int($id) && $next[0 ] === $id) {
// _extractCodeBlocs() {{{
* Extract all <code></code> blocs in the given raw docstring.
* @param string $docstring raw docstring
* @return array an array of code blocs strings.
private function _extractCodeBlocs ($docstring)
// extract <code></code> blocks, we use preg_match_all because there
// could be more than one code block by docstring
$rx = '/<code>[\s\*]*(<[\?\%](php)?)?\s*'
. '(.*?)\s*([\?\%]>)?[\s\*]*<\/code>/si';
if (isset ($tokens[3 ]) && is_array($tokens[3 ])) {
foreach ($tokens[3 ] as $i => $token) {
if ($this->_hasStandaloneDoctest ($token)) {
$testfile_contents = $this->_handleStandaloneDoctest ($token);
if ($testfile_contents !== false ) {
// replace the current doctest code with the contents
// of the external included file
$token = $testfile_contents;
if (!$this->_hasDocTest ($token)) {
// _hasStandaloneDoctest() {{{
* Return true if the string data provided contains an external doctest file.
* @param string $data The docstring data
private function _hasStandaloneDoctest ($data)
* Return true if the string data provided contains a doctest.
* @param string $data string data
private function _hasDocTest ($data)
// _handleStandaloneDoctest() {{{
* Return the contents of the external doctest file.
* @param string $docbloc The docstring data
* @return mixed boolean or string
private function _handleStandaloneDoctest ($docbloc)
foreach ($lines as $i => $l) {
if (preg_match(" /^\s*$p\s?($k):\s*(.*)$/" , $l, $matches)) {
" Unable to read standalone doctest file \"$f\""
$rx = '/(<[\?\%](php)?)?(.*?)([\?\%]>)?/si';
// _handleDoctestLine() {{{
* Parse the doctest line provided.
* @param string $line the line of code to parse
* @throws Testing_DocTest_Exception
private function _handleDoctestLine ($line)
$states = array (null , self ::STATE_FLAGS , self ::STATE_DOCTEST ,
self ::STATE_SKIP_IF , self ::STATE_INI_SET , self ::STATE_TMPL_CODE );
if (!in_array($this->_state , $states)) {
$this->_testCase ->altname .= $line;
$this->_state = self ::STATE_DOCTEST;
// _handleFlagsLine() {{{
* Parse the flag line provided.
* @param string $line The flag line to parse
* @throws Testing_DocTest_Exception
private function _handleFlagsLine ($line)
$states = array (null , self ::STATE_FLAGS , self ::STATE_DOCTEST ,
self ::STATE_SKIP_IF , self ::STATE_INI_SET , self ::STATE_TMPL_CODE );
if (!in_array($this->_state , $states)) {
foreach ($flags as $flag) {
$this->_testCase ->flags |= constant($const);
$this->_state = self ::STATE_FLAGS;
// _handleExpectsLine() {{{
* Parse the expects line provided.
* @param string $line the expects line to parse
* @throws Testing_DocTest_Exception
private function _handleExpectsLine ($line)
$states = array (self ::STATE_CODE , self ::STATE_EXPECTS ,
if (!in_array($this->_state , $states)) {
throw new Exception (" unexpected expects line: $line" );
$this->_testCase ->expectedValue .= $line;
// handle line continuation
$this->_testCase ->expectedValue .= "\n";
$this->_testCase ->expectedValue
= trim($this->_testCase ->expectedValue , '\\');
$this->_state = self ::STATE_EXPECTS;
// _handleExpectsFileLine() {{{
* Parse the expects-file line provided.
* @param string $line the expects-file line to parse
* @throws Testing_DocTest_Exception
private function _handleExpectsFileLine ($line)
$states = array (self ::STATE_CODE , self ::STATE_EXPECTS_FILE ,
if (!in_array($this->_state , $states)) {
throw new Exception (" unexpected expects-file line: $line" );
$this->_testCase ->expectedValue = $contents;
$this->_state = self ::STATE_EXPECTS_FILE;
* Parse the code line provided.
* @param string $line the code line to parse
* @throws Testing_DocTest_Exception
private function _handleCodeLine ($line)
$states = array (self ::STATE_EXPECTS , self ::STATE_EXPECTS_FILE ,
$this->_testCase ->code .= rtrim($line) . "\n";
$this->_state = self ::STATE_CODE;
// _handleSkipIfLine() {{{
* Parse the skip-if line provided.
* @param string $line the skip-if line to parse
* @throws Testing_DocTest_Exception
private function _handleSkipIfLine ($line)
$states = array (null , self ::STATE_FLAGS , self ::STATE_DOCTEST ,
self ::STATE_SKIP_IF , self ::STATE_INI_SET , self ::STATE_TMPL_CODE );
if (!in_array($this->_state , $states)) {
$this->_testCase ->skipIfCode .= rtrim($line) . "\n";
$this->_state = self ::STATE_SKIP_IF;
// _handleIniSetLine() {{{
* Parse the ini-set line provided.
* @param string $line the ini-set line to parse
* @throws Testing_DocTest_Exception
private function _handleIniSetLine ($line)
$states = array (null , self ::STATE_FLAGS , self ::STATE_DOCTEST ,
self ::STATE_SKIP_IF , self ::STATE_INI_SET , self ::STATE_TMPL_CODE );
if (!in_array($this->_state , $states)) {
$this->_testCase ->iniSettings [$a[0 ]] = $a[1 ];
$this->_state = self ::STATE_INI_SET;
* Parse the tmpl-code line provided.
* @param string $line the ini-set line to parse
* @throws Testing_DocTest_Exception
private function _handleTmplCode ($line)
$states = array (null , self ::STATE_FLAGS , self ::STATE_DOCTEST ,
self ::STATE_SKIP_IF , self ::STATE_INI_SET , self ::STATE_TMPL_CODE );
if (!in_array($this->_state , $states)) {
" Malformed tmpl-code line:
$this->_testCase ->tmplCode= $tmplfile;
$this->_state = self ::STATE_TMPL_CODE;
// _handleCleanLine() {{{
* Parse the clean line provided.
* @param string $line the clean line to parse
* @throws Testing_DocTest_Exception
private function _handleCleanLine ($line)
$states = array (self ::STATE_EXPECTS , self ::STATE_EXPECTS_FILE ,
if (!in_array($this->_state , $states)) {
$this->_testCase ->cleanCode .= rtrim($line) . "\n";
$this->_state = self ::STATE_CLEAN;
// _handleSetupLine() {{{
* Parse the setup line provided.
* @param string $line the setup line to parse
* @throws Testing_DocTest_Exception
private function _handleSetupLine ($line)
$states = array (self ::STATE_CODE , self ::STATE_EXPECTS ,
self ::STATE_EXPECTS_FILE , self ::STATE_CLEAN );
$this->_testCase ->setupCode .= rtrim($line) . "\n";
$this->_state = self ::STATE_SETUP;
// _handleLineContinuation() {{{
* Parse a line continuation.
* @param string $line the line to parse
private function _handleLineContinuation ($line)
case self ::STATE_EXPECTS:
$this->_handleExpectsLine ($line);
$this->_handleFlagsLine ($line);
case self ::STATE_DOCTEST:
$this->_handleDoctestLine ($line);
case self ::STATE_SKIP_IF:
$this->_handleSkipIfLine ($line);
case self ::STATE_INI_SET:
$this->_handleIniSetLine ($line);
$this->_handleCleanLine ($line);
$this->_handleSetupLine ($line);
case self ::STATE_TMPL_CODE;
$this->_handleTmplCode ($line);
Documentation generated on Mon, 11 Mar 2019 15:52:27 -0400 by phpDocumentor 1.4.4. PEAR Logo Copyright © PHP Group 2004.
|