Skip to content

Instantly share code, notes, and snippets.

@splitbrain
Created January 24, 2016 12:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save splitbrain/c009308fa138f61d7042 to your computer and use it in GitHub Desktop.
Save splitbrain/c009308fa138f61d7042 to your computer and use it in GitHub Desktop.
DokuWiki CLI script I used to automatically create pull requests for all DokuWiki plugins available on github that had wrong method signatures
#!/usr/bin/php
<?php
if(!defined('DOKU_INC')) define('DOKU_INC', realpath(dirname(__FILE__) . '/../') . '/');
define('NOSESSION', 1);
require_once(DOKU_INC . 'inc/init.php');
class FixerCLI extends DokuCLI {
protected $dir;
protected $user = 'splitbrain';
protected $token = 'PUT API TOKEN HERE';
protected $org = 'splitbrain-forks';
protected $branch = 'php7-signature';
protected $message = 'Adjust method signatures to match parent';
protected $details = <<<EOF
Starting with PHP 7, classes that overide methods from their parent have to match their parent's method signature regarding type hints. Otherwise PHP will throw an error.
Your plugin seems not to match DokuWiki's method signatures causing it to fail under PHP7. This patch tries to fix that.
Please note that this patch and the resulting pull request were created using an automated tool. Something might have gone, wrong so please carefully check before merging.
If you think the code submitted is wrong, please feel free to close this PR and excuse the mess.
EOF;
/**
* Register options and arguments on the given $options object
*
* @param DokuCLI_Options $options
* @return void
*/
protected function setup(DokuCLI_Options $options) {
// TODO: Implement setup() method.
}
/**
* Your main program
*
* Arguments and options have been parsed when this is run
*
* @param DokuCLI_Options $options
* @return void
*/
protected function main(DokuCLI_Options $options) {
$this->dir = sys_get_temp_dir() . '/' . md5(time());
io_mkdir_p($this->dir);
$repos = $this->getRepositories();
foreach($repos as list($user, $repo)) {
$this->info("--- $user/$repo -----------------------------------------------");
if(!$this->cloneRepo($user, $repo)) continue;
$this->adjustRepo();
if(!$this->commitChanges()) continue;
if(!$this->forkRepo($user, $repo)) continue;
if(!$this->pushToFork($repo)) continue;
$this->sendPullRequest($user, $repo);
}
io_rmdir($this->dir, true);
}
/**
* Pushes all changes to the forked directory
*
* @param $repo
* @return bool
*/
protected function pushToFork($repo) {
$url = escapeshellarg("git@github.com:{$this->org}/$repo.git");
$branch = escapeshellarg($this->branch);
system("git push $url $branch", $ret);
if($ret != 0) {
$this->error('Git returned non-zero exit code when pushing');
return false;
}
$this->success('Changes pushed to fork');
return true;
}
/**
* Commits all the changes if any
* @return bool
*/
protected function commitChanges() {
system('git diff --quiet --exit-code', $ret);
if($ret == 0) {
$this->info('No changes');
return false;
}
$message = escapeshellarg($this->message);
system('git commit -a -m ' . $message, $ret);
if($ret != 0) {
$this->error('Git returned non-zero exit code when commiting');
return false;
}
$this->success('Changes committed');
return true;
}
/**
* execute change to repo
*/
protected function adjustRepo() {
// syntax plugins
$files = array_merge(glob('syntax.php'), glob('syntax/*.php'));
foreach($files as $file) {
$code = file_get_contents($file);
$this->signatureCheck($code, 'handle', array('', '', '', 'Doku_Handler'));
$this->signatureCheck($code, 'render', array('', 'Doku_Renderer', ''));
file_put_contents($file, $code);
}
// action plugins
$files = array_merge(glob('action.php'), glob('action/*.php'));
foreach($files as $file) {
$code = file_get_contents($file);
$this->signatureCheck($code, 'register', array('Doku_Event_Handler'));
file_put_contents($file, $code);
}
//system('git diff');
}
/**
* Adjust the given code to match the correct signature
*
* @param string $content The code to adjust
* @param string $func Name of the function
* @param string[] $hints List of type hints. Empty string when none
*/
protected function signatureCheck(&$content, $func, $hints) {
$func = preg_quote($func, '/');
$rehint = '( *\w+ )?';
$revar = '( *&?\$\w+ *)';
// build the regex
$regex = "(^|\\s)(function +$func *)\\(";
foreach($hints as $h) {
$regex .= $rehint . $revar;
$regex .= ',';
}
$regex = rtrim($regex, ',');
$regex .= '\)';
// find the old function line
if(!preg_match("/$regex/i", $content, $m)) return;
$match = array_shift($m); // whole match
$this->info('Old: ' . $match);
// build the new function line
$newline = array_shift($m); // leading whitespace
$newline .= array_shift($m); // function name as written
$newline .= '(';
foreach($hints as $h) {
$hint = array_shift($m);
$var = array_shift($m);
if($h) $var = preg_replace('/&/', '', $var); // no pass-by-reference for objects
if(strtolower(trim($hint)) != strtolower($h)) {
if(substr($newline, -1) != '(') $newline .= ' ';
$newline .= $h;
if($var[0] != ' ') $newline .= ' ';
} else {
$newline .= $hint;
}
$newline .= $var;
$newline .= ',';
}
$newline = rtrim($newline, ',');
$newline .= ')';
$this->info('New: ' . $newline);
// replace old with new
$content = preg_replace("/$regex/i", $newline, $content, 1);
}
/**
* Clones the given repository and changes into it.
*
* @param $user
* @param $repo
* @return bool
*/
protected function cloneRepo($user, $repo) {
$cloneurl = escapeshellarg("https://github.com/$user/$repo.git");
$this->info("Cloning from $cloneurl");
chdir($this->dir);
system('git clone --depth 1 ' . $cloneurl, $ret);
if($ret != 0) {
$this->error("Git returned non-zero error code");
return false;
}
chdir($repo);
system('git checkout master'); // always base on master
if($ret != 0) {
$this->error("Git returned non-zero error code");
return false;
}
system('git checkout -b ' . escapeshellarg($this->branch));
if($ret != 0) {
$this->error("Git returned non-zero error code");
return false;
}
$this->success("Repository cloned to " . getcwd());
return true;
}
/**
* Create a fork and wait for it to materialize
*
* @param $user
* @param $repo
* @return bool
*/
protected function forkRepo($user, $repo) {
$http = new DokuHTTPClient();
$http->user = $this->user;
$http->pass = $this->token;
$http->headers['Accept'] = 'application/vnd.github.v3+json';
$this->info("Forking $user/$repo...");
$data = $http->post("https://api.github.com/repos/$user/$repo/forks?organization={$this->org}", '');
if($data === false) {
$this->error("Failed to fork");
$this->error($http->resp_body);
return false;
}
// wait for the fork to have happened
for($i = 0; $i < 20; $i++) {
$data = $http->get("https://api.github.com/repos/{$this->org}/$repo");
if($data !== false) {
$this->success("Fork available at {$this->org}/$repo");
return true;
}
echo '.';
sleep(5);
}
echo "\n";
$this->error("The fork was not created within the expected time");
return false;
}
/**
* Send a pull request
*
* @param $user
* @param $repo
* @return bool
*/
protected function sendPullRequest($user, $repo) {
$http = new DokuHTTPClient();
$http->user = $this->user;
$http->pass = $this->token;
$http->headers['Accept'] = 'application/vnd.github.v3+json';
$pr = array(
'title' => $this->message,
'body' => $this->details,
'head' => $this->org . ':' . $this->branch,
'base' => 'master' // this might be wrong for some repos!?
);
$data = $http->post("https://api.github.com/repos/$user/$repo/pulls", json_encode($pr));
if($data === false) {
$this->error("Failed to send PR");
$this->error($http->resp_body);
return false;
}
$this->success("Sent Pull Request");
return true;
}
/**
* Get all github repositories
*
* @return array
*/
protected function getRepositories() {
$this->info('loading extension data...');
$http = new DokuHTTPClient();
$data = $http->get('https://www.dokuwiki.org/lib/plugins/pluginrepo/api.php?fmt=php');
$list = unserialize($data);
$repos = array();
foreach($list as $ext) {
if(!isset($ext['sourcerepo'])) continue;
if(!preg_match('/github.com\/(\w+)\/(\w+)(\/|$)/', $ext['sourcerepo'], $m)) continue;
// bundled plugins are weird
if($m[1] == 'splitbrain' && $m[2] == 'dokuwiki') {
continue;
}
$repos[] = array($m[1], $m[2]);
}
$this->success(sprintf('found %d github repositories', count($repos)));
return $repos;
}
}
$fixer = new FixerCLI();
$fixer->run();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment