* @license GPL 2 (http://www.gnu.org/licenses/gpl.html) * @package syntax */ if (!defined("DOKU_INC")) die(); // the plugin must be run within Dokuwiki require_once "method.php"; // common methods used by PyCode plugin /** * This class defines all the methods used by the PyCode plugin to produce * the plugin's output. * * It extends DokuWiki's basic syntax defined in lib/plugins/syntax.php. * * @package syntax_pycode */ class syntax_plugin_pycode extends DokuWiki_Syntax_Plugin { /** * Constructor method for class suitable for any initialization. */ public function __construct() { $this->mpp = new method_pycode_plugin; } /** * Define the syntax types that this plugin applies when founds * its token: it is simply replaced. * * @return (str) */ public function getType() { return "substition"; } /** * Define how the plugin's output is handled regarding paragraphs. * * Open paragraphs will be closed before plugin output and * the plugin output will not starts with a paragraph: *

foo

* *

bar

* * @return (str) */ public function getPType() { return "block"; } /** * Define the priority used to determine in which order modes are added: * the mode with the lowest sort number will win. * * Since this plugin provides text codes and internal links, it is * sorted at: * Doku_Parser_Mode_code (= 200) < PyCode plugin * Doku_Parser_Mode_internallink (= 310) < PyCode plugin * * @return (int) */ public function getSort() { return 315; } /** * Define the regular expression needed to match the plugin's syntax. * * This plugin use the following general syntax: * * * @param (str) $mode name for the format mode of the final output * (at the present only $mode == "xhtml" is supported) */ public function connectTo($mode) { $this->Lexer->addSpecialPattern("", $mode, "plugin_pycode"); } /** * Handle the plugin's syntax matched. * * This method is called every time the parser encounters the * plugin's syntax in order to produce a list of instructions for the * renderer, which will be interpreted later. * * @param (str) $match the text matched * @param (int) $state the lexer state * @param (int) $pos the character position of the matched text * @param (obj) $handler object reference to the Doku_Handler class, * defined in inc/parser/handler.php * @return (arr) $data it contains the parameters handed to the * plugin's syntax and found in the wiki page, that is: * array { * [0] => (str) * [1] => (str) * [2] => (str) * [3] => (str) * } * where only element [0] is obligatory so the following * are valid combinations: * * c * f * f * l */ public function handle($match, $state, $pos, Doku_Handler $handler) { $data = array(); $match = $this->mpp->_remove_multi_space($match); $match = substr($match, 8, -1); // strips "" // get user option for line numbers $usr_nums = null; $re = "/(-nums)(\s*)(=)(\s*)(0|1)/"; preg_match($re, $match, $matches); if (preg_match($re, $match, $matches) == 1) { $usr_nums = $matches[5]; } // get user option for title $usr_title = null; $re = "/(-title)(\s*)(=)(\s*)(\'|\")(.*)(\'|\")/"; preg_match($re, $match, $matches); if (preg_match($re, $match, $matches) == 1) { $usr_title = $matches[6]; } // get user option for docstrings $usr_docstr = null; $re = "/(-docstr)(\s*)(=)(\s*)(0|1)/"; preg_match($re, $match, $matches); if (preg_match($re, $match, $matches) == 1) { $usr_docstr = $matches[5]; } // remove user options and create data array $re = "/(\s*-nums.*|\s*-title.*|\s*-docstr.*)/"; $subst = ""; $match = preg_replace($re, $subst, $match); $data = explode(" ", $match); // append user options $data["usr_nums"] = $usr_nums; $data["usr_title"] = $usr_title; $data["usr_docstr"] = $usr_docstr; return $data; } /** * Process the list of instructions that render the final output. * * It's used an array to remember if, in the same wiki page, * the same is called one or more times, in order to do only one * connection to the relative repository to download its code. * As soon as the content of is downloaded, it's stored in a local * directory and used to render the output, even for the oher calls * of . * If, visiting another wiki page, the same is called again or * every time the page is refreshed, this array is unset (desired effect) * so, before download the code again, it's checked how much * up-to-date is the local copy of against the original copy * in the repository. * The array is of the form: * array { * [] => (int) 0|1|2 * } * with the following meaning: * = 0: has been just downloaded so it's up-to-date * = 1: has been downloaded in the current session * and can be reused in the same page * = 2: was downloaded in a previous session so * it could be out-of-date * * Since is saved a copy of , it is necessary to know when it's no * longer used in any wiki page. * Hence it's used a logfile in which are recorded, for each wiki page, all * the repository used. When all the references to the same repository are * deleted from all wiki pages, the relative local copy of is * deleted too. * * @param (str) $mode name for the format mode of the final output * (at the present only $mode == "xhtml" is supported) * @param (obj) $renderer object reference to the Doku_Render class, * defined in /inc/parser/renderer.php, which defines functions for the * output (see also /lib/styles/screen.css for some styles) * @param (arr) $data it contains the parameters handled to the * plugin's syntax and found in the wiki page, that is: * array { * [0] => (str) * [1] => (str) * [2] => (str) * [3] => (str) * ["usr_nums"] => (str) -nums = 0|1 * ["usr_title"] => (str) -title = "" * ["usr-docstr"] => (str) -docstr = 0|1 * } * where only element [0] is obligatory and element [3] is * the name of the class at wich the method belongs to * so the following are valid combinations: * * [c [-docstr]] * [f [] [-nums] [-title]] * [l [-nums] [-title]] */ public function render($mode, Doku_Renderer $renderer, $data) { global $INPUT; $repo = array(); // store all (s) found in the current page $code_error = array("error", "notfound-lns", "notfound-def", "notfound-cls"); // eventualy disable the cache $cache = $this->getConf("cache"); if ($cache == 1){ $renderer->nocache(); } try { // check if url to the source code is right $src_url = $data[0]; $src_url = $this->mpp->_check_src_url($src_url); if (in_array($src_url, $code_error)) { throw new Exception($this->mpp->_check_src_url($src_url)); } // get info associated to the code $flag = $data[1]; $name = $data[2]; $subname = $data[3]; $nums = $data["usr_nums"]; $title = $data["usr_title"]; $docstr = $data["usr_docstr"]; list($name_host, $name_repo, $name_brch, $name_file) = $this->mpp->_get_names($src_url); $loc_url = $this->mpp->_get_loc_url($name_host, $name_repo, $name_brch, $name_file); $lang = $this->mpp->_get_lang($name_file); $raw_url = $this->mpp->_get_raw_url($src_url); // check if in this session has been alredy downloaded if ((file_exists($loc_url) == false)) { $repo[$src_url] = 0; } elseif (file_exists($loc_url) == true and in_array($src_url, array_keys($repo)) == false) { $repo[$src_url] = 2; } // get the code if ($flag == "" and $name != "") { throw new Exception("wrong-flag"); } elseif ($flag != "" and $name == "") { throw new Exception("wrong-flag"); } elseif (($flag == "f" or $flag == "c") and $lang != "python") { throw new Exception("wrong-flag"); } if ($repo[$src_url] == 0) { // get the whole code list($code_raw, $sl_raw, $el_raw) = $this->mpp->_get_code($raw_url); if (in_array($code_raw, $code_error)) { throw new Exception($code_raw); } // save in local and use this instead of the repo $this->mpp->_save_code($loc_url, $code_raw); } elseif ($repo[$src_url] == 2) { list($code_raw, $sl_raw, $el_raw) = $this->mpp->_get_code($raw_url, $flag, $name, $subname); if (in_array($code_raw, $code_error)) { throw new Exception($code_raw); } if ($flag == "c") { // temporaly save in local and use this instead of the repo // because to chek if a class is changed is necessary to // check if all its methods are changed too $tmp_raw_loc_url = $loc_url . ".tmp"; $this->mpp->_save_code($tmp_raw_loc_url, $code_raw); } } list($code_loc, $sl_loc, $el_loc) = $this->mpp->_get_code($loc_url, $flag, $name, $subname); if (in_array($code_loc, $code_error)) { throw new Exception($code_loc); } // call the printer if ($repo[$src_url] == 0 or $repo[$src_url] == 1) { if (in_array($flag, array("", "l", "f"))) { $range = range($sl_loc, $el_loc); $this->_print_code($renderer, $src_url, $code_loc, $lang, $range, $flag, $name, $subname, $nums, $title); } elseif ($flag == "c") { $tree_loc = $this->mpp->_get_tree($code_loc); $this->_print_tree($renderer, $loc_url, $tree_loc, $docstr); } $repo[$src_url] = 1; } elseif ($repo[$src_url] == 2) { $code_dif = $this->mpp->_get_code_dif($code_raw, $code_loc); if (in_array($flag, array("", "l", "f"))) { if ($code_dif == "dif") { $this->_print_note($renderer, $loc_url, $code_raw, $sl_loc, $el_loc); $range = range($sl_loc, $el_loc); } else { $range = range($sl_raw, $el_raw); // sth. is changed outside the range } $this->_print_code($renderer, $src_url, $code_loc, $lang, $range, $flag, $name, $subname, $nums, $title); } elseif ($flag == "c") { $tree_loc = $this->mpp->_get_tree($code_loc); if ($code_dif == "dif") { $tree_raw = $this->mpp->_get_tree($code_raw); $tree_dif = $this->mpp->_get_tree_dif($tmp_raw_loc_url, $loc_url, $tree_raw, $tree_loc); $this->_print_note($renderer, $loc_url, $code_raw, $sl_loc, $el_loc); $this->_print_tree($renderer, $loc_url, $tree_dif, $docstr); } else { $this->_print_tree($renderer, $loc_url, $tree_loc, $docstr); } unlink($tmp_raw_loc_url); } } // record the (s) embedded in this wiki page $tmp_log_url = DOKU_PLUGIN . "pycode/tmp/logfile.tmp"; $this->mpp->_save_code($tmp_log_url, $loc_url); } catch (Exception $error) { if ($error->getMessage() == "error") { $this->_print_error($renderer); } elseif ($error->getMessage() == "wrong-flag") { $this->_print_wrong_flag($renderer); } elseif ($error->getMessage() == "notfound-lns") { $this->_print_notfound_lns($renderer); } elseif ($error->getMessage() == "notfound-def") { $this->_print_notfound_def($renderer); } elseif ($error->getMessage() == "notfound-cls") { $this->_print_notfound_cls($renderer); } } } /** * Print the code of . * * @param (obj) $renderer object reference to the Doku_Render class, * defined in /inc/parser/renderer.php, which defines functions for the * output (see also /lib/styles/screen.css for some styles) * @param (str) $src_url the url to the source code of in the repo * Bitbucket = * "https://bitbucket.org///src//" * GitHub = * "https://github.com///blob//" * @param (arr) $code the code to embed * @param (str) $lang the language name used in * @param (arr) $range the numbers of the lines matching the code to embed * @param (str) $flag flag to indicate what kind of code we want to embed: * "" = all | "c" = class | "f" = function | "l" = lines * @param (str) $name if specified, it can be only: * name-function|lines * @param (str) $subname if specified, it can be only: * name-class * @param (int) $nums if specified, it can be only: * 0 = hide line numbers | 1 = show line numbers * @param (str) $title if specified, it is the new user's title for the * code embedded */ private function _print_code(Doku_Renderer $renderer, $src_url, $code, $lang, $range, $flag, $name = null, $subname = null, $nums = null, $title = null) { if ($flag == "f") { $code = $this->mpp->_remove_indent($code); } $code = implode($code, PHP_EOL); if ($nums === null) { $nums = $this->getConf("nums"); } // make the title for the code if ($title === null) { $title = $this->getConf("title"); if ($title != "none") { if ($title == "file ⋅ class ⋅ def ⋅ #:#") { $src = basename($src_url) . " ⋅ "; } elseif ($title == "src-url ⋅ class ⋅ def ⋅ #:#") { $src = $src_url . " ⋅ "; } if ($flag == "") { $class = ""; $def = ""; } elseif ($flag == "f") { if ($subname === null) { $class = ""; $def = "def: " . $name . " ⋅ "; } else { $class = "class: " . $subname . " ⋅ "; $def = "def: " . $name . " ⋅ "; } } elseif ($flag == "l") { $class = ""; $def = ""; } $lns = "Ln: " . reset($range) . "-" . end($range); $title = "$src" . "$class" . "$def" . "$lns"; } } else { if ($title == "") { $title = "none"; } } // print if ($nums == 1) { $renderer->doc .= "
"; $renderer->doc .= "
"; if ($title != "none") { $renderer->doc .= "
 
"; } $renderer->doc .= "
"; $renderer->doc .= "
";
            foreach ($range as $number) {
                if (strlen(end($range)) <= 4) {
                    $number = str_pad($number, 4, " ", STR_PAD_LEFT);
                }
                else {
                    $number = str_pad($number, 5, " ", STR_PAD_LEFT);
                }
                $renderer->doc .= "$number
"; } $renderer->doc .= "
"; $renderer->doc .= "
"; } if ($title != "none") { $icon = $this->mpp->_get_icon($src_url); $renderer->doc .= "
"; $renderer->doc .= "
$title
"; $renderer->doc .= "
"; } $renderer->doc .= "
";
        $renderer->doc .= $this->mpp->_get_geshi_code($code, $lang);
        $renderer->doc .= "
"; if ($title != "none") { $renderer->doc .= "
"; } if ($nums == 1) { $renderer->doc .= "
"; } } /** * Print the tree structure of a given class defined in . * * @param (obj) $renderer object reference to the Doku_Render class, * defined in /inc/parser/renderer.php, which defines functions for the * output (see also /lib/styles/screen.css for some styles) * @param (str) $loc_url the url to the raw code of the copy of in * /lib/plugins/pycode/tmp//// * @param (arr) $tree the name of a class and its methods, that is: * array { * [0] => array { * [0] => (str) "class ()" * [1] => (str) * [2] => (int) <#> * } * [i] => array { * [0] => (str) "class ()" * [1] => (str) * [2] => (int) <#> * } * } * where <#> is a flag used by the print function of the tree * structure of the given class and which tells "how" to render * each name; it can takes the following values: * <#> = 0 means "unchanged" * <#> = 1 means "changed" * <#> = 2 means "deleted" * <#> = 3 means "added" * @param (int) $docstr if specified, it can be only: * 0 = hide docstring | 1 = show docstring */ private function _print_tree(Doku_Renderer $renderer, $loc_url, $tree, $docstr = null) { global $INFO; $namespace = $INFO["namespace"]; if ($docstr === null) { $docstr = $this->getConf("docstr"); } // get the descriptive part of the docstring if ($docstr == 1) { $all_brief = array(); foreach ($tree as $key => $val) { if ($key == 0) { list($code, $sl, $el) = $this->mpp->_get_code($loc_url, "c", $val[1]); if ($code != "notfound-cls") { $brief = $this->mpp->_get_docstr($code, $val[1], "c"); } } else { list($code, $sl, $el) = $this->mpp->_get_code($loc_url, "f", $val[1], $tree[0][1]); if ($code != "notfound-def") { $brief = $this->mpp->_get_docstr($code, $val[1], "f"); } } array_push($all_brief, $brief); } } // this is what we want to produce: // * class () // * def () // * ... // * def () // // the equivalent xhtml code is: //
    //
  • //
    class ()
    //
      //
    • //
      def ()
      //
    • // ... //
    • //
      def ()
      //
    • //
    //
  • //
$renderer->listu_open(); //
    $renderer->listitem_open(1); //
  • $renderer->listcontent_open(); //
    if ($tree[0][2] == 1) { $renderer->strong_open(); // start bold $renderer->cdata($tree[0][0]); $renderer->strong_close(); // stop bold } else { $renderer->cdata($tree[0][0]); } if ($docstr == 1) { $renderer->doc .= "
    "; $renderer->emphasis_open(); foreach ($all_brief[0] as $line) { $renderer->doc .= $line . "
    "; } $renderer->emphasis_close(); $renderer->doc .= "
    "; } $renderer->listcontent_close(); //
    $renderer->listu_open(); //
      start indented foreach ($tree as $key => $val) { if ($key == 0) { continue; } $renderer->listitem_open(2); //
    • $renderer->listcontent_open(); //
      if ($val[2] == 1) { $renderer->strong_open(); // start bold } elseif ($val[2] == 2) { $renderer->deleted_open(); // start strike out } $renderer->internallink($namespace . ":" . $val[1], $val[0]); if ($val[2] == 1) { $renderer->strong_close(); // stop bold } elseif ($val[2] == 2) { $renderer->deleted_close(); // stop strike out } if ($docstr == 1) { $renderer->doc .= "
      "; $renderer->emphasis_open(); foreach ($all_brief[$key] as $line) { $renderer->doc .= $line . "
      "; } $renderer->emphasis_close(); $renderer->doc .= "
      "; } $renderer->listcontent_close(); //
      $renderer->listitem_close(); //
    • } $renderer->listu_close(); //
    stop indented $renderer->listitem_close(); //
  • $renderer->listu_close(); //
} /** * Print a notificaton which informs that the copy of * is out-of-date. * * Only users with a level permission greater than or equal to 2, * can flush it away (see DokuWiki's ACL): * level | 0 1 2 4 8 16 255 * permission| none read edit create upload delete admin * * @param (obj) $renderer object reference to the Doku_Render class, * defined in /inc/parser/renderer.php, which defines functions for the * output (see also /lib/styles/screen.css for some styles) * @param (str) $loc_url the url to the raw code of the copy of in * /lib/plugins/pycode/tmp//// * @param (arr) $code_new the updated code got from the repository * @param (int) $sl the number of the starting line of the code * @param (int) $el the number of the ending line of the code */ private function _print_note(Doku_Renderer $renderer, $loc_url, $code_new, $sl, $el) { global $INFO; $perm = $INFO["perm"]; $note = $this->getLang("note"); $code_new = base64_encode(serialize($code_new)); $renderer->doc .= "
"; $renderer->cdata($note); if ($perm >= AUTH_EDIT) { $renderer->doc .= "
"; $renderer->doc .= ""; $renderer->doc .= ""; $renderer->doc .= ""; $renderer->doc .= ""; $renderer->doc .= ""; $renderer->doc .= "
"; } $renderer->doc .= "
"; } /** * Print an error message which informs that the code between the * specified lines is out of range. * * @param (obj) $renderer object reference to the Doku_Render class, * defined in /inc/parser/renderer.php, which defines functions for the * output (see also /lib/styles/screen.css for some styles) */ private function _print_notfound_lns(Doku_Renderer $renderer) { $notfound_lns = $this->getLang("notfound-lns"); $renderer->doc .= "
"; $renderer->cdata($notfound_lns); $renderer->doc .= "
"; } /** * Print an error message which informs that the given function was * not found in . * * @param (obj) $renderer object reference to the Doku_Render class, * defined in /inc/parser/renderer.php, which defines functions for the * output (see also /lib/styles/screen.css for some styles) */ private function _print_notfound_def(Doku_Renderer $renderer) { $notfound_def = $this->getLang("notfound-def"); $renderer->doc .= "
"; $renderer->cdata($notfound_def); $renderer->doc .= "
"; } /** * Print an error message which informs that the given class was * not found in . * * @param (obj) $renderer object reference to the Doku_Render class, * defined in /inc/parser/renderer.php, which defines functions for the * output (see also /lib/styles/screen.css for some styles) */ private function _print_notfound_cls(Doku_Renderer $renderer) { $notfound_cls = $this->getLang("notfound-cls"); $renderer->doc .= "
"; $renderer->cdata($notfound_cls); $renderer->doc .= "
"; } /** * Prints an error message which informs that the flag is not correct. * * @param (obj) $renderer object reference to the Doku_Render class, * defined in /inc/parser/renderer.php, which defines functions for the * output (see also /lib/styles/screen.css for some styles) */ private function _print_wrong_flag(Doku_Renderer $renderer) { $wrong_flag = $this->getLang("wrong-flag"); $renderer->doc .= "
"; $renderer->cdata($wrong_flag); $renderer->doc .= "
"; } /** * Print an error message which informs that the file is not * a Python script. * * @param (obj) $renderer object reference to the Doku_Render class, * defined in /inc/parser/renderer.php, which defines functions for the * output (see also /lib/styles/screen.css for some styles) */ private function _print_wrong_lang(Doku_Renderer $renderer) { $wrong_lang = $this->getLang("wrong-lang"); $renderer->doc .= "
"; $renderer->cdata($wrong_lang); $renderer->doc .= "
"; } /** * Print an error message which informs that there was some problems * to contact the repository. * * @param (obj) $renderer object reference to the Doku_Render class, * defined in /inc/parser/renderer.php, which defines functions for the * output (see also /lib/styles/screen.css for some styles) */ private function _print_error(Doku_Renderer $renderer) { $error = $this->getLang("error"); $renderer->doc .= "
"; $renderer->cdata($error); $renderer->doc .= "
"; } }