'Perforce', 'version' => '@'.$version.'', 'url' => 'http://public.perforce.com/wiki/Perforce_MediaWiki_extension', 'author' => array('Matt Attaway', 'Sam Stafford', 'Marc Wensauer'), 'description' => 'Display Perforce data in wiki pages', ); function wfSetupPerforce( &$parser ) { $parser->setHook( 'p4change', 'tagP4Change' ); $parser->setHook( 'p4changes', 'tagP4Changes' ); $parser->setHook( 'p4print', 'tagP4Print' ); $parser->setHook( 'p4variants', 'tagP4Variants' ); $parser->setFunctionHook( 'p4changes', 'parseP4Changes' ); $parser->setFunctionHook( 'p4chgcats', 'parseP4ChgCats' ); $parser->setFunctionHook( 'p4graph', 'parseP4Graph' ); $parser->setFunctionHook( 'p4info', 'parseP4Info' ); $parser->setFunctionHook( 'p4job', 'parseP4Job' ); $parser->setFunctionHook( 'p4jobs', 'parseP4Jobs' ); $parser->setFunctionHook( 'p4print', 'parseP4Print' ); $parser->setFunctionHook( 'p4variants', 'parseP4Variants' ); return true; } function wfPerforceLanguageGetMagic( &$magicWords, $langCode = "en" ) { switch( $langCode ) { default: # mind the ws and Ws. $magicWords['p4changes'] = array ( 0, 'p4changes' ); $magicWords['p4chgcats'] = array ( 0, 'p4chgcats' ); $magicWords['p4graph'] = array ( 0, 'p4graph' ); $magicWords['p4info'] = array ( 0, 'p4info' ); $magicWords['p4job'] = array ( 0, 'p4job' ); $magicWords['p4jobs'] = array ( 0, 'p4jobs' ); $magicWords['p4print'] = array ( 0, 'p4print' ); $magicWords['p4variants'] = array ( 0, 'p4variants' ); } return true; } function wfPerforceParserOutput( &$outputPage, $parserOutput ) { if ( !empty( $parserOutput->mPerforceJavascriptTag ) ) { global $wgJsMimeType, $wgScriptPath; $outputPage->addScript( "\n" ); } return true; } class Perforce extends SpecialPage { function Perforce() { SpecialPage::SpecialPage("Perforce"); } function execute( $par ) { global $wgRequest, $wgOut; $this->setHeaders(); $param = $wgRequest->getText('param'); $pars = explode( '/', $par ); $fn = array_shift( $pars ); $args = explode( '@@', implode( '/', $pars ) ); switch( $fn ) { case 'changes': return $this->execChanges( $args ); case 'graph': return $this->execGraph( $args ); case 'info': return $this->execInfo( $args ); case 'job': return $this->execJob( $args ); case 'jobs': return $this->execJobs( $args ); case 'print': return $this->execPrint( $args ); case 'variants': return $this->execVariants( $args ); } return $this->execDefault(); } function execDefault() { global $wgP4WEBURL, $wgOut; $wgOut->setPagetitle( 'Perforce' ); $out = <<addWikiText( $out ); } function execChanges( $args ) { global $wgOut, $wgParser; if ( $args[1] && $args[1] != '//...' ) $toPath = ' to '.$args[1]; if ( $args[3] ) $byUser = ' by user '.$args[3]; if ( $toPath || $byUser ) $made = ' made'; if ( !$args[0] ) $args[0] = '40'; $wgOut->setPagetitle( 'Last '.$args[0].' changes'.$made.$toPath.$byUser ); $wgOut->addWikiText( parseP4Changes( $wgParser, $args[0], $args[1], $args[2], $args[3] ) ); } function execGraph( $args ) { global $wgOut, $wgParser; $wgOut->setPagetitle( 'Graph of '.$args[0] ); $wgOut->addWikiText( parseP4Graph( $wgParser, $args[0], $args[1], $args[2] ) ); } function execInfo( $args ) { global $wgOut, $wgParser; $wgOut->setPagetitle( 'Perforce server info' ); $wgOut->addWikiText( parseP4Info( $wgParser ) ); } function execJob( $args ) { global $wgOut, $wgParser; $wgOut->setPagetitle( 'Perforce job '.$args[0].' '.$args[1] ); array_unshift( $args, $wgParser ); $out = call_user_func_array( 'parseP4Job', $args ); $wgOut->addWikiText( $out[0] ); } function execJobs( $args ) { global $wgOut, $wgParser; $args = str_replace( '_', ' ', $args ); $wgOut->setPagetitle( 'Perforce jobs: '.$args[0] ); if ( !$args[0] ) $wgOut->setPagetitle( 'Perforce jobs' ); array_unshift( $args, $wgParser ); $out = call_user_func_array( 'parseP4Jobs', $args ); $wgOut->addWikiText( $out[0] ); } function execPrint( $args ) { global $wgOut, $wgParser; $wgOut->setPagetitle( $args[0] ); $mode = $args[1]; if ( !$mode ) $mode = 'raw'; $wgOut->addWikiText( parseP4Print( $wgParser, $args[0], $mode ) ); } function execVariants( $args ) { global $wgOut; $wgOut->setPagetitle( 'Variants of '.$args[0] ); $wgOut->addHtml( getP4Variants( $args[0] ) ); } } # {{#p4changes:num|path|brief/long/full|user}} function parseP4Changes( &$parser, $num = '', $path = '', $desc = '', $user = '' ) { if ( !$path ) $path = '//...'; $tag = ' false); } # {{#p4chgcats:path|fmt|keyword1|cat1|keyword2|cat2|...}} function parseP4ChgCats( &$parser, $path = '', $pre = '*', $fmt = '' ) { $parser->disableCache(); $cats = array(); // list of cats $maps = array(); // key->cat mappings // Build array of categories and keyword map. $arg = 4; //number of named args while ( func_num_args() > $arg + 1 ) { $key = func_get_arg( $arg ); $arg++; $cat = func_get_arg( $arg ); $arg++; $maps[ $key ] = $cat; if ( !in_array( $cat, $cats ) ) { $cats[] = $cat; $chgs[$cat] = array(); } } // Get ze changes! $cmdline = buildP4Cmd(); $cmdline .= ' -Ztag -Zspecstring changes -l -m 10000 '.wfEscapeShellArg( $path ); $output = array(); // mock up specdef for getSpecFields() $output[] = '... specdef change; ;;time; ;;user; ;;client; ;;status; ;;desc; ;;'; exec( $cmdline, $output ); $data = getSpecFields( $output ); array_shift( $data ); // shift off field list $result = ''; foreach( $cats as $cat ) { $result .= $cat."\n"; foreach( $data as $chg ) { $show = false; foreach( $maps as $key => $map ) { if ( $map != $cat ) continue; if ( strpos( $chg['desc'], $key ) === false ) continue; $show = true; } if ( !$show ) continue; $desc = $chg['desc']; formatField( $fmt, $desc ); $result .= $pre.''.$chg['change'].': '; $result .= $desc."\n"; } } return array($result, 'noparse' => false); } function tagP4Changes( $input, $argv, $parser ) { return renderRecentChanges( $input, $argv, $parser, 'html' ); } # {{#p4graph:path|constraint|server|dot}} function parseP4Graph( &$parser, $path, $const= '', $server = '', $dot = '' ) { global $wgP4DVER; $flag = '-1 '; if ( $wgP4DVER && $wgP4DVER < 2009.1 ) $flag = ''; $rankdir = 'LR'; if ( $const == 'file' ) $rankdir = 'TD'; $cmdline = buildP4Cmd( $server ); $cmdline .= ' filelog ' . $flag . wfEscapeShellArg( $path ) . ' 2>&1'; $filelog = array(); exec( $cmdline, $filelog ); $out = "\n"; $out .= ' digraph G { rankdir='.$rankdir.'; node [shape=box];'; $out .= "\n"; $todo = array(); $done = array(); $changes = array(); getP4Graph( $filelog, $out, $todo, $done, $changes, $const ); while ( count( $todo ) ) { $file = array_pop( $todo ); if ( !$file ) continue; $filelog = array(); $cmdline = buildP4Cmd( $server ); $cmdline .= ' filelog ' . $flag . wfEscapeShellArg( $file ); exec( $cmdline, $filelog ); getP4Graph( $filelog, $out, $todo, $done, $changes, $const ); } if ( $const == 'change' && count( $changes ) > 1 ) { array_unique( $changes ); sort( $changes, SORT_NUMERIC ); $fchange = array_shift( $changes ); $out .= ' "'.$fchange.'" [style=invis];'."\n"; $out .= ' "'.$fchange; $lchange = array_pop( $changes ); foreach( $changes as $change ) { $out .= '" -> "'.$change.'" [style=invis weight=100];'."\n"; $out .= ' "'.$change.'" [style=invis];'."\n"; $out .= ' "'.$change; } $out .= '" -> "'.$lchange.'" [style=invis weight=100];'."\n"; $out .= ' "'.$lchange.'" [style=invis];'."\n"; } if ( $const == 'file' && count( $done ) > 1 ) { $files = $done; array_unique( $files ); sort ( $files ); $ffile = array_shift( $files ); $out .= ' "'.$ffile.'" [style=invis];'."\n"; $out .= ' "'.$ffile; $lfile = array_pop( $files ); foreach( $files as $file ) { $out .= '" -> "'.$file.'" [style=invis weight=100];'."\n"; $out .= ' "'.$file.'" [style=invis];'."\n"; $out .= ' "'.$file; } $out .= '" -> "'.$lfile.'" [style=invis weight=100];'."\n"; $out .= ' "'.$lfile.'" [style=invis];'."\n"; } if ( $dot ) $out .= ' '.$dot."\n"; $out .= " }\n"; $out .= ""; if ( !count($done) ) return $out; #error #Trim paths if possible. $firstFile = array_shift( $done ); array_unshift( $done, $firstFile ); $firstCmn = $firstFile; $lastCmn = $firstFile; foreach( $done as $file ) { $firstCmn = strFirstCommon( $firstCmn, $file ); $lastCmn = strLastCommon( $lastCmn, $file ); } #If there's only one file, just use the filename. if ( $firstCmn == $firstFile ) { $firstCmn = substr($firstFile,0,-1*(strlen(strrchr($firstFile,'/'))-1)); $lastCmn = ''; } $firstLen = strlen( $firstCmn ); $lastLen = strlen( $lastCmn ); foreach( $done as $file ) { $newname = substr( $file, $firstLen ); if ( $lastLen ) $newname = substr( $newname, 0, -1 * $lastLen ); $out = str_replace( $file, $newname, $out ); } return array($out, 'noparse' => false); } # {{#p4info:}} function parseP4Info( &$parser ) { $out = ''; $cmdline = buildP4Cmd() . ' info'; $info = array(); exec( $cmdline, $info ); $out .= "{| id=p4info\n"; foreach( $info as $line ) { $regs = array(); if ( !preg_match( '/^(Server [^:]+:)(.*)/', $line, $regs ) ) continue; $out .= "|-\n"; $out .= "| '''$regs[1]'''\n"; $out .= "| \n"; $out .= "| $regs[2]\n"; } $out .= "|}"; return $out; } # {{#p4job:job|field|format}} function parseP4Job( &$parser, $job = '', $field = '', $format = '' ) { if ( $job == '' ) { return array( 'Please specify a job.', 'noparse' => false ); } global $wgP4WEBURL; if ( $field == '' ) { return array( '['.$wgP4WEBURL.$job.'?ac=111 '.$job.']', 'noparse' => false ); } $parser->disableCache(); $cmdline = buildP4Cmd(); $cmdline .= ' -Ztag -Zspecstring job -o ' . wfEscapeShellArg( $job ); $output = array(); exec( $cmdline, $output ); $fields = getSpecFields( $output ); $result = isset($fields[$job][strtolower($field)]) ? $fields[$job][strtolower($field)] : ''; if ( $format ) formatField( $format, $result ); return array( $result, 'noparse' => false ); } # {{#p4jobs:expr|fields|maxjobs|format| # tableattr|trattr|tdattr|query1|action1|...}} function parseP4Jobs( &$parser, $expr='', $fields='', $maxjobs='', $format='', $tableattr='', $trattr='', $tdattr='' ) { $parser->disableCache(); if ( !$maxjobs ) $maxjobs = '20'; $mjobs = intval($maxjobs); if ( $mjobs > 100 || $mjobs < 1 ) $mjobs = 100; $cmdline = buildP4Cmd(); $cmdline .= ' -Ztag -Zspecstring jobs -r -m'.$mjobs; $expr = htmlspecialchars_decode( $expr ); // angle brackets! if ( $expr ) $cmdline .= ' -e '.wfEscapeShellArg( $expr ); $output = array(); exec( $cmdline, $output ); $data = getSpecFields( $output ); $rules = array(); $arg = 8; //number of standard args while ( func_num_args() > $arg + 1 ) { $rule = array(); $rule['query'] = func_get_arg( $arg ); $arg++; $rule['action'] = func_get_arg( $arg ); $arg++; $rule['jobs'] = array(); applyJobQuery( $rule, $data, $expr, $mjobs ); $rules[] = $rule; } if ( $fields ) { $fields = preg_split( '/\s+/', $fields ); } else { $fields = array_slice( $data['@'], 0, 5 ); } array_shift( $data ); # shift off the specdef data sort( $data ); $fields = array_reverse( $fields ); foreach ( $fields as &$fref ) { $o = +1; if ( substr( $fref, 0, 1 ) == '!' ) { $o = -1; $fref = substr( $fref, 1 ); } # Insert current ordering information into array elements. Ech. foreach( $data as $k => &$jref ) { $jref['@'] = $k; } usort( $data, create_function ( '$a,$b', 'return compKey($a,$b,"'.addslashes(strtolower($fref)).'",'.$o.');' ) ); } $fields = array_reverse( $fields ); $index = array(); foreach( $data as $k => &$row ) { $index[$row['job']] = $k; $row['@attrib'] = ''; $row['@format'] = ''; foreach( $rules as &$rule ) { if ( $rule['query'] == 'ALL' ) $rule['jobs'][] = $row['job']; if ( $rule['query'] == 'ODD' && $k % 2 == 1 ) $rule['jobs'][] = $row['job']; if ( $rule['query'] == 'EVEN' && $k % 2 == 0 ) $rule['jobs'][] = $row['job']; } } applyJobRules( $rules, $data, $index ); $result = '{|'.$tableattr."\n"; $result .= '|-'.$trattr."\n"; foreach( $fields as $f ) { $result .= '!'.$f."\n"; } $result .= "\n"; foreach( $data as $job ) { $result .= '|-'.$trattr.$job['@attrib']."\n"; foreach( $fields as $f ) { $value = $job[strtolower($f)]; $result .= '|'.$tdattr.'|'; if ( $f == 'Job' ) $value = '{{#p4job:'.$value.'}}'; formatField( $format.$job['@format'], $value, $f ); $result .= $value; $result .= "\n"; } $result .= "\n"; } $result .= '|}'; return array( $result, 'noparse' => false ); } # {{#p4print:path|raw/text/wiki}} function parseP4Print( &$parser, $path = '', $mode = 'raw' ) { if ( $mode == 'raw' ) { return array("", 'noparse' => false); } $cmdline = buildP4Cmd(); $cmdline .= ' print -q ' . wfEscapeShellArg( $path ); $cmdline .= ' 2>&1'; $text = ""; $output = array(); exec( $cmdline, $output ); foreach( $output as $line ) { if ( $mode == 'text' ) { $line = ' ' . $line; } $text = $text . $line . "\n" ; } return $text; } function tagP4Print( $input, $argv, $parser ) { $text = '
';
    $text .= htmlspecialchars( parseP4Print( $parser, $argv['path'], 'wiki' ) );
    $text .= '
'; return $text; } # {{#p4variants:path}} function parseP4Variants( &$parser, $path = '' ) { return array('', 'noparse' => false); } function tagP4Variants( $input, $argv, $parser ) { global $wgUseAjax, $wgScriptPath; if ( !$wgUseAjax ) return "This MediaWiki instance does not support Ajax."; $parser->mOutput->mPerforceJavascriptTag = true; # flag for use by wfPerforceParserOutput $path = $argv["path"]; if( $path == '' ) return "Please provide a depot path (path attribute)."; $nocache = time(); $html = ''; $html .= '
'; $html .= '
'; $html .= renderPerforcePathLink( $path, TRUE ); $html .= ' ['; $html .= 'addText( getP4Variants( $path ) ); return getP4Variants( $path ); return $response; } function tagP4Change( $input, $argv, $parser ) { global $wgP4EXEC; global $wgP4PORT; global $wgP4USER; global $wgP4PASSWD; global $wgP4WEBURL; if( isset($argv["style"]) ) { $range = '@' . $input . ',' . $input; $cmdline = wfEscapeShellArg( $wgP4EXEC ) . " -u " . wfEscapeShellArg( $wgP4USER ) . " -p " . wfEscapeShellArg( $wgP4PORT ); if( $wgP4PASSWD != "" ) $cmdline .= " -P " . wfEscapeShellArg( $wgP4PASSWD ); $cmdline .= " changes " . wfEscapeShellArg( $range ); $text = `{$cmdline}`; return formatShortChange( $text ); } else return "" . htmlspecialchars( $input ) . ""; } function renderRecentchanges( $input, $argv, &$parser, $format ) { $parser->disableCache(); $path = isset($argv["path"]) ? $argv["path"] : ""; $num = isset($argv["num"]) ? $argv["num"] : ""; $desc = isset($argv["desc"])? $argv["desc"] : ""; $user = isset($argv["user"])? $argv["user"] : ""; if( $path == "" || $num == "" ) return "Please provide both a depot path(path attribute) and the number of changes to show(num attribute)."; $cmdline = buildP4Cmd(); $cmdline .= " changes -s submitted -m" . escapeshellarg( $num ) . " "; if ( $desc == "long" ) $cmdline .= "-L "; if ( $desc == "full" ) $cmdline .= "-l "; if ( $user != "" ) $cmdline .= "-u " . escapeshellarg( $user ) . " "; if ( $path != '//...' ) $cmdline .= escapeshellarg( $path ) . " "; $cmdline .= '2>&1'; $changes = array(); exec( $cmdline, $changes ); $isDarkStripe = true; $output = "
    "; $inList = false; $inListList = false; foreach( $changes as $value ) { if ( !preg_match( '/^Change [0-9]+ on/', $value ) ) { if ( !$inListList ) { $output .= '
      '; $inListList = true; } $output .= htmlspecialchars( $value ); if ( $value ) $output .= '
      '; continue; } if ( $inListList ) { $output .= '
    '; $inListList = false; } if ( $inList ) { $output .= "\n"; $inList = false; } if( $isDarkStripe ) $output .= "
  • "; else $output .= "
  • "; $inList = true; $output .= formatShortChange( $value, $format ); $isDarkStripe = !$isDarkStripe; } if ( $inListList ) { $output .= '
'; $inListList = false; } if ( $inList ) { $output .= "\n"; $inList = false; } $output .= ""; return $output; } function formatShortChange( $text, $format='html', $newwin=FALSE ) { global $wgP4WEBURL; $target = ''; if ( $newwin ) $target = 'target="_blank" '; $pattern[0] = '/Change (\d+) /'; $pattern[1] = '/ (\S+)@(\S+)/'; switch( $format ) { default: case 'html': $replacement[0] = 'Change ${1} '; $replacement[1] = ' ${1}@${2} '; break; case 'wiki': $replacement[0] = 'Change [' . $wgP4WEBURL . '${1}?ac=10 ${1}] '; $replacement[1] = ' [' . $wgP4WEBURL . '${1}?ac=17 ${1}]@[' . $wgP4WEBURL . '${2}?ac=15 ${2}] '; break; } return preg_replace( $pattern, $replacement, $text ); } function renderPerforcePathLink( $path, $newwin=FALSE ) { global $wgP4WEBURL; $target = ''; $ac = 22; if ( ( strpos( $path, '...' ) !== FALSE ) || ( strpos( $path, '*' ) !== FALSE ) ) $ac = 69; if ( $newwin ) $target = 'target="_blank" '; return ''.htmlspecialchars( $path ).''; } function getP4Variants( $path ) { global $wgP4EXEC; global $wgP4PORT; global $wgP4USER; global $wgP4CLIENT; global $wgP4PASSWD; $path = trim( $path ); if ( substr( $path, -1 ) == '/' ) { $path .= '...'; } $html = '
    '; $p4Cmd = $wgP4EXEC . " -u " . $wgP4USER . " -p " . $wgP4PORT . " -c " . $wgP4CLIENT; if( $wgP4PASSWD != "" ) $p4Cmd .= " -P " . $wgP4PASSWD; #Infer branch relationships from integed output. $toFile = ''; $fromFile = ''; $pleft = $path; $pwild = ''; $pright = ''; if ( substr( $path, -1 ) == '*' ) { $pleft = substr( $path, 0, -1 ); $pwild = substr( $path, -1 ); } if ( substr( $path, -3 ) == '...' ) { $pleft = substr( $path, 0, -3 ); $pwild = substr( $path, -3 ); } $pllen = strlen( $pleft ); $prlen = strlen( $pright ); if ( ( strpos( $pleft, '...' ) !== FALSE ) || ( strpos( $pleft, '*' ) !== FALSE ) ) return 'Embedded wildcard in '.htmlspecialchars( $path ).' -- unable to figure out branch relationships.'; $cmdline = $p4Cmd . ' -Ztag integrated ' . escapeshellarg( $path ); $descriptors = array ( 1 => array("pipe", "w") ); $branches = array(); $integedproc = proc_open( $cmdline, $descriptors, $pipes ); if ( !is_resource($integedproc) ) return 'Unable to get branch information.'; while ( !feof($pipes[1]) ) { $line = substr( fgets( $pipes[1] ), 0, -1 ); #chomp newline if ( substr( $line, 0, 11 ) == '... toFile ' ) $toFile = substr( $line, 11 ); if ( substr( $line, 0, 13 ) == '... fromFile ' ) $fromFile = substr( $line, 13 ); if ( $toFile && $fromFile ) { #toFile is our path, even if it's really the "from" of the integ. Handy! if ( substr( $toFile, 0, $pllen ) == $pleft ) #this should always be true { $pright = substr( $toFile, $pllen ); $prlen = strlen( $pright ); if ( !$prlen || substr( $fromFile, -$prlen ) == $pright ) #check for a path match { if ( $prlen ) $branch = substr( $fromFile, 0, -$prlen ) . $pwild; else $branch = $fromFile; $branches[$branch] = $branch; } } $toFile = ''; $fromFile = ''; } } sort ( $branches ); if ( !count( $branches ) ) { return '
      No branches found.
    '; } set_time_limit( 300 ); #We now have a list of $branches that each corresponds to $path. foreach( $branches as $branch ) { $cmdline = $p4Cmd . ' interchanges ' . escapeshellarg( $branch ) . ' ' . escapeshellarg( $path ); $changes = array(); exec( $cmdline, $changes ); $bvar = FALSE; foreach( $changes as $change ) { $cnum = ''; $cnums = array(); if ( preg_match( '/^Change (\d+) /', $change, $cnums ) ) $cnum = $cnums[1]; else continue; $cvar = FALSE; $cmdline = $p4Cmd . ' -Ztag integrate -n ' . escapeshellarg( $branch.'@'.$cnum.',@'.$cnum ) . ' ' . escapeshellarg( $path ); $integs = array(); exec( $cmdline, $integs ); foreach( $integs as $integ ) { if ( $integ == '... action integrate' ) { $cvar = TRUE; break; } } if ( $cvar ) { if ( !$bvar ) { $bvar = TRUE; $html .= '
  • '; $html .= renderPerforcePathLink( $branch, TRUE ); $html .= '
      '; } $html .= '
    • '; $html .= formatShortChange( $change, 'html', TRUE ); $html .= '
    • '; } } if ( $bvar ) { $html .= '
  • '; } } if ( strpos( $html, '
  • ' ) === FALSE ) { $html .= '
  • No branches have outstanding changes.
  • '; } $html .= '
'; return $html; } # Takes array of filelog output and running count # of what files need doing and what files are done. # Returns GraphViz output. function getP4Graph( &$filelog, &$out, &$todo, &$done, &$changes, &$const ) { $file = ''; $rev = ''; $error = ''; $dirty = FALSE; foreach( $filelog as $line ) { $regs = array(); if ( preg_match( '/^... ... (.*) (\/\/[^#]*)#(.*)/', $line, $regs ) ) { if ( !in_array($regs[2],$todo) && !in_array($regs[2],$done) ) { array_push ( $todo, $regs[2] ); } if ( strpos( $regs[1], ' by' ) || strpos( $regs[1], ' into' ) ) continue; $lbl = $regs[1]; $clr = 'blue'; $sty = 'solid'; switch ( $lbl ) { case 'branch from': case 'copy from': case 'delete from': $lbl = substr( $lbl, 0, -5 ); break; case 'moved from': $lbl = substr( $lbl, 0, -5 ); $clr = 'red'; break; case 'merge from': $lbl = substr( $lbl, 0, -5 ); $sty = 'dashed'; break; case 'ignored': $lbl = substr( $lbl, 0, -1 ); $sty = 'dotted'; break; case 'edit from': $lbl = substr( $lbl, 0, -5 ); $sty = 'dashed'; $clr = 'red'; break; } if ( $dirty ) $clr = 'red'; $src = $regs[3]; if ( strpos( $src, '#' ) ) $src = substr( $src, strpos( $src, '#' ) + 1 ); if ( $file && $rev ) { $out .= ' "'.$regs[2].'#'.$src.'" -> "'.$file.'#'.$rev.'"'; $out .= ' [label="'.$lbl.'" color="'.$clr.'" fontcolor="'.$clr.'" style="'.$sty.'" weight=1];'; $out .= "\n"; } } else if ( preg_match( '/^... #([0-9]+) change ([0-9]+) ([a-z\/]+) /', $line, $regs ) ) { if ( $file && $rev ) { $out .= ' "'.$file.'#'.$regs[1].'" -> "'.$file.'#'.$rev.'" [weight=1000];'."\n"; } $rev = $regs[1]; $dirty = ( $regs[3] == 'edit' || $regs[3] == 'add' ); if ( $const == 'change' ) { array_push( $changes, $regs[2] ); $out .= ' { rank=same "'.$file.'#'.$regs[1].'" -> "'.$regs[2].'" [style=invis]; }'."\n"; } if ( $const == 'file' ) { $out .= ' { rank=same "'.$file.'#'.$regs[1].'" -> "'.$file.'" [style=invis]; }'."\n"; } } else if ( preg_match( '/^\/\//', $line, $regs ) ) { $file = $line; $rev = ''; array_push( $done, $file ); if ( in_array( $file, $todo ) ) { unset( $todo[array_search($file,$todo)] ); } } else $error .= ' ' .$line . "\n"; } if ( $error ) { $out = ' ' . substr( $error, 1 ) . "\n" . $out; } return $out; } function buildP4Cmd( $server='' ) { global $wgP4EXEC; global $wgP4PORT; global $wgP4USER; global $wgP4PASSWD; global $wgP4ALTPORTS; $cmdline = $wgP4EXEC . " -u " . $wgP4USER ; if( $wgP4PASSWD != "" ) $cmdline .= " -P " . $wgP4PASSWD ; if ( $server == '' || $server == $wgP4PORT ) { $cmdline .= " -p " . $wgP4PORT; } else { if ( is_array( $wgP4ALTPORTS) && in_array( $server, $wgP4ALTPORTS ) ) $cmdline .= " -p " . $server; else $cmdline .= " -p " . $wgP4PORT; } return $cmdline; } #returns the common leading substring of two strings, split on '/'. function strFirstCommon( $str1, $str2 ) { $path1 = explode( '/', $str1 ); $path2 = explode( '/', $str2 ); $cmn = array(); while ( count( $path1 ) > 1 && count( $path2 ) > 1 ) { $s1 = array_shift( $path1 ); $s2 = array_shift( $path2 ); if ( $s1 != $s2 ) break; array_push( $cmn, $s1 ); } if ( !count( $cmn ) ) return ''; return implode( '/', $cmn ) . '/'; } #returns the common trailing substring of two strings, split on '/'. function strLastCommon( $str1, $str2 ) { $path1 = explode( '/', $str1 ); $path2 = explode( '/', $str2 ); $cmn = array(); while ( count( $path1 ) > 1 && count( $path2 ) > 1 ) { $s1 = array_pop( $path1 ); $s2 = array_pop( $path2 ); if ( $s1 != $s2 ) break; array_unshift( $cmn, $s1 ); } if ( !count( $cmn ) ) return ''; return '/' . implode( '/', $cmn ); } #takes an array of "p4 -Ztag -Zspecstring specs..." output, #returns an array of arrays of spec fields. function getSpecFields( $output ) { $fields = array(); $fields['@'] = array(); $specstring = array_shift( $output ); $specstring = substr( $specstring, 12 ); # remove '... specdef ' $fields['@'] = preg_split( '/;;/', $specstring ); foreach( $fields['@'] as &$field ) { $field = array_shift( preg_split( '/;/', $field ) ); } $id = $fields['@'][0]; # name of spec type, e.g. 'job' $i = -1; $spec = array(); foreach( $output as $line ) { $line .= "\n"; $m = array(); if ( preg_match( '/^\.\.\. ([^\s]+)/', $line, $m ) ) { if ( $m[1] == $id ) { # Start of a new spec. if ( array_key_exists( strtolower($id), $spec ) ) { foreach( $spec as &$f ) { $f = chop( $f ); } $fields[$spec[strtolower($id)]] = $spec; } $i = -1; $spec = array(); } if ( in_array( $m[1], $fields['@'] ) && array_search( $m[1], $fields['@'] ) > $i ) { # Start of a new field. $line = substr( $line, 5 + strlen($m[1]) ); #remove '... Field ' $i = array_search( $m[1], $fields['@'] ); $spec[strtolower($fields['@'][$i])] = ''; } } if ( $i < 0 ) { continue; } $spec[strtolower($fields['@'][$i])] .= $line; } if ( array_key_exists( strtolower($id), $spec ) ) { foreach( $spec as &$f ) { $f = chop( $f ); } $fields[$spec[strtolower($id)]] = $spec; } return $fields; } # Compare two arrays by value associated with a particular key. function compKey( $arr1, $arr2, $key, $order = 1 ) { # Sort arrays that don't have the key to the front for easy removal. if ( !array_key_exists( $key, $arr1 ) && !array_key_exists( $key, $arr2 ) ) return 0; if ( !array_key_exists( $key, $arr1 ) ) return -1; if ( !array_key_exists( $key, $arr2 ) ) return +1; if ( $arr1[$key] < $arr2[$key] ) return -1 * $order; if ( $arr1[$key] > $arr2[$key] ) return +1 * $order; # Existing order is kept in ['@']; preserve if there. if ( array_key_exists( '@', $arr1 ) && array_key_exists( '@', $arr2 ) ) { if ( $arr1['@'] < $arr2['@'] ) return -1; if ( $arr1['@'] > $arr2['@'] ) return +1; } return 0; } # Takes a formatting string and field to format (in-place). function formatField( $format, &$field, $fname = '' ) { $field = str_replace( '!', '!', str_replace( '|', '|', $field ) ); $fname = strtolower( $fname ); $fmt = preg_split( '/\s+/', $format ); foreach( $fmt as $f ) { $hpos = strrpos( $f, '#' ); if ( $hpos ) { $flimit = strtolower( substr( $f, $hpos + 1 ) ); if ( $flimit && $flimit != $fname ) continue; $f = substr( $f, 0, $hpos ); } if ( !strncasecmp( $f, 'template:', 9 ) ) { $f = substr( $f, 9 ); $field = '{{'.$f.'|'.$field.'}}'; continue; } if ( strpos( $f, 'chars' ) ) { $field = substr( $field, 0, intval( substr( $f, 0, -5 ) ) ); } if ( strpos( $f, 'words' ) ) { $words = explode( ' ', $field ); $words = array_slice( $words, 0, intval( substr( $f, 0, -5 ) ) ); $field = implode( ' ', $words ); } if ( strpos( $f, 'lines' ) ) { $lines = explode( "\n", $field ); $lines = array_slice( $lines, 0, intval( substr( $f, 0, -5 ) ) ); $field = implode( "\n", $lines ); } if ( strpos( $f, 'paras' ) ) { $paras = explode( "\n\n", $field ); $paras = array_slice( $paras, 0, intval( substr( $f, 0, -5 ) ) ); $field = implode( "\n\n", $paras ); } if ( strpos( $f, 'sents' ) ) { $sents = preg_split( '/(\s+[^\s]+\s+[^\s]+[\.!?]+\s+)/', $field, 0, PREG_SPLIT_DELIM_CAPTURE ); $sents = array_slice( $sents, 0, 2 * intval( substr( $f, 0, -5 ) ) ); $field = implode( '', $sents ); } if ( $f == 'line' ) { $field = trim( preg_replace( '/\s+/', ' ', $field ) ); } if ( $f == 'raw' && strpos( $field, "\n" ) ) { $field = '
'.$field.'
'; } if ( $f == 'text' && strpos( $field, "\n" ) ) { $lines = explode( "\n", $field ); $field = "\n ".implode( "\n ", $lines ); } } } # Annotate an rule array with list of matching jobs. function applyJobQuery( &$rule, $table, $basequery, $maxjobs ) { $query = $rule['query']; $query = trim( $query ); if ( $query == 'ALL' || $query == 'ODD' || $query == 'EVEN' ) return; $query = strtolower( $query ); $field = ''; $value = ''; if ( !strpos( $query, ' ' ) && !strpos( $query, '&' ) && !strpos( $query, '|' ) && !strpos( $query, '^' ) && !strpos( $query, '*' ) ) { // We can handle simple queries by ourselves. I hope. $pair = explode( '=', $query ); if ( count( $pair ) == 2 ) { $field = $pair[0]; $value = $pair[1]; } else if ( count( $pair ) == 1 ) { $value = $pair[0]; } } if ( $value ) { // Handle it. foreach ( $table as $row ) { $found = false; if ( $field ) { if ( stripos( $row[$field], $value ) !== false ) $found = true; } else { foreach ( $row as $k => $fv ) { if ( stripos( $fv, $value ) !== false ) { $found = true; } } } if ( $found ) $rule['jobs'][] = $row['job']; } } else { // Couldn't handle it. Run a new job query. if ( $basequery ) $newquery = '('.$basequery.') ('.$query.')'; else $newquery = $query; $cmdline = buildP4Cmd(); $cmdline .= ' jobs -m '.$maxjobs.' -e '.escapeshellarg( $newquery ); $jobs = array(); exec( $cmdline, $jobs ); foreach( $jobs as $j ) { $jf = explode( ' ', $j ); if ( array_key_exists( $jf[0], $table ) ) $rule['jobs'][] = $jf[0]; } } } # Process per-row job "rules" and annotate job table with results. function applyJobRules( $rules, &$table, $index ) { foreach( $rules as $rule ) { $key = ''; if ( !strcasecmp( substr( $rule['action'], 0, 7 ), 'attrib:' ) ) { $key = '@attrib'; $action = substr( $rule['action'], 7 ); } if ( !strcasecmp( substr( $rule['action'], 0, 7 ), 'format:' ) ) { $key = '@format'; $action = substr( $rule['action'], 7 ); } if ( !$key ) continue; foreach( $rule['jobs'] as $j ) { $table[$index[$j]][$key] .= ' '.$action; } } } ?>