Pocco |
|
---|---|
|
|
Pocco is a PHP port of Docco under a The MIT License. Copyright (C) 2011 Kibleur C. Docco is a quick-and-dirty, literate-programming-style documentation generator. It produces HTML that displays your comments alongside your code. Comments are passed through Creole thanks to a modified version of Ivan Fomichev's Creole 1.0 extensible parser, or Michel Fortin's PHP-Markdown Extra and the code is passed through the Prism syntax highlighter. This page is the result of running Pocco against its own source file. Another SampleYou can see the result of running Pocco against the original docco.coffee file just here DownloadYou can Download Pocco here.
In the future, Pocco will support as many languages
its internal highlighter Prism. |
|
Let's look at our source code |
|
We need to include Creole, Markdown and Prism modules |
include_once('libs/creole.php');
include_once('libs/markdown.php');
include_once('libs/Prism.php'); |
The main class |
class Pocco { |
Constructor takes a $filename as argument |
function __construct($file_name, $file_extension, $markup_engine = 'creole')
{
$this->markup_engine = $markup_engine;
$this->filename = $file_name;
$this->file_extension = $file_extension;
$this->source = file_get_contents($file_name.'.'.$file_extension); |
This sets a Prism instance with an HtmlFormatter |
$this->form = new HtmlFormatter();
$this->hil = new Prism($this->form, $file_extension); |
All supported languages are set inside an hash-table |
$this->langs = array(
"php" => array( 'single' => '/^\s*\/\//',
'multi' => array( 'start' => '#/\*#', 'middle' => '#\*#', 'end' => '#\*/#'),
"coffee" => array( 'single' => '/^#/'), |
'multi' => array( 'start' => '/^###/', 'middle' => '/^#/', 'end' => '/^[^#]/')), |
); |
Sets the language dictionnary |
$this->set_language();
}
function set_language()
{
$dico = $this->langs[$this->file_extension]; |
It is assumed that each language provides a single_line_comment. |
$this->single_line_comment = $dico['single'];
if (array_key_exists('ignore',$dico)) {
$this->ignore = $dico['ignore'];
} |
Now, check if multi is set |
if (array_key_exists('multi',$dico)) {
$this->block_comment_start = $dico['multi']['start'];
$this->block_comment_mid = $dico['multi']['middle'];
$this->block_comment_end = $dico['multi']['end'];
}
}
function parse() {
$sections = array();
$docs = array();
$code = array();
$lines = explode("\n", $this->source); |
Skip the first line if it is a shebang. |
if (preg_match('/^\#\!/', $lines[0])) { array_shift($lines); }
$in_comment_block = false;
foreach($lines as $line) { |
If a language sets an ignore key, then the line matching it is just removed from the output. |
if (isset($this->ignore) AND preg_match($this->ignore, $line)) {
continue;
} |
If we're currently in a comment block, check whether the line matches the end of a comment block. |
if ($in_comment_block) {
if (isset($this->block_comment_end) AND preg_match($this->block_comment_end, $line)) {
$in_comment_block = false;
}
else {
if (isset($this->block_comment_mid)) {$docs[] = preg_replace($this->block_comment_mid, '', $line);}
else { $docs[] = $line;}
}
} |
Otherwise, check whether the line matches the beginning of a block, or a single-line comment all on it's lonesome. In either case, if there's code, start a new section. |
else {
if (isset($this->block_comment_start) AND preg_match($this->block_comment_start, $line)) {
$in_comment_block = true;
if (!empty($code)) {
$sections[] = array($docs, $code);
$docs = array();
$code = array();
}
}
else if (isset($this->single_line_comment) AND preg_match($this->single_line_comment, $line)) {
if (!empty($code)) {
$sections[] = array($docs, $code);
$docs = array();
$code = array();
}
$docs[] = preg_replace($this->single_line_comment, '', $line);
}
else {
$code[] = $line;
}
}
} // end foreach
if ( (!empty($docs)) OR (!empty($code)) ) {
$sections[] = array($docs, $code);
}
if ($this->markup_engine == 'markdown') {
$sections = $this->normalize_leading_spaces($sections);
}
return $sections;
}
function normalize_leading_spaces($sections) {
$sec = array();
foreach($sections as $section) {
if ( (!empty($section)) AND (!empty($section[0])) ) {
if (preg_match('/^\s+/', $section[0][0], $matches)) {
$fist_line_blanks = '/^'.$matches[0].'/';
$res = array();
foreach($section as $line) {
$res[] = preg_replace($fist_line_blanks , '', $line);
}
$truc = $res;
}
}
else { $truc = $section;}
$sec[] = $truc;
}
return $sec;
} |
Get the contents of our given $filename template. Then replace any placeholder like [[place_holder]] inside At the moment, there is only one placeholder inside the templates: the [[filename]]. |
function get_tpl_contents($filename)
{
$output = file_get_contents('templates/'.$filename . '.tpl');
$regex = '/\[\[([a-zA-Z0-9_]+)\]\]/';
while(preg_match($regex, $output)) {
$output = preg_replace_callback(
$regex,
array($this, "cb_replace")
,$output);
}
return $output;
} |
Callback method for preg_replace_callback used inside the get_file_contents method. |
function cb_replace($matches) {
$dico = array(
'filename' => $this->filename
);
return $dico[$matches[1]];
} |
Take the list of paired sections two-tuples and split into two separate lists: one holding the comments with leaders removed and one with the code blocks. |
function split($sections) {
$docs_blocks = array();
$code_blocks = array();
foreach($sections as $sec) {
$docs = implode("\n",$sec[0]);
$code = implode("\n",$sec[1]);
$docs_blocks[] = $docs;
$code_blocks[] = $code;
}
return array($docs_blocks, $code_blocks);
} |
The main method |
function build_doc()
{ |
Separate docs & source |
$res = $this->split($this->parse());
$db = $res[0]; // docs blocks
$cb = $res[1]; // code blocks |
Computes the lenght |
$nb = max(count($db), count($cb)); |
Our output will contain sections |
$out = array(); |
The result is starting here: we place the HTML header |
$result = $this->get_tpl_contents('head'); |
Iterate over all found sections |
for($i=0;$i < $nb; $i++) {
$source = $db[$i]; |
echo "'$source'\n"; Process the docs with Creole markup |
if ($this->markup_engine == 'creole') {
$cr = new creole();
$out['doc'][$i] = $cr->parse($source);
}
else { // We use Markdown
$out['doc'][$i] = Markdown($source);
} |
Process the highlighting of the source code with Prism |
$out['src'][$i] = $this->hil->from_string($cb[$i]); |
Now build each section |
$doc = $out['doc'][$i];
$src = $out['src'][$i]; |
Using a Heredoc string with variables inside to build each section inside an HTML table |
$section =<<<EOF
<tr id='section-$i'>
<td class=docs>
$doc
</td>
<td class=code>
<div class='highlight'><pre class="code">$src</pre></div>
</td>
</tr>
EOF;
$result .= $section;
} // end for
$result .= $this->get_tpl_contents('foot');
return $result;
} |
Save the doc to HTML given a $output_name filename Note: the .html extension is automatically added. |
function save_doc($output_name)
{
$res = $this->build_doc($this->source);
$out = $output_name . '.html';
$fp = fopen($out, 'w');
fwrite($fp,$res);
fclose($fp);
echo "Your file '$out' has been saved.\n Thanks for using Pocco.\n";
}
} |