summaryrefslogtreecommitdiff
path: root/www/wiki/tests/parser/TestFileEditor.php
diff options
context:
space:
mode:
Diffstat (limited to 'www/wiki/tests/parser/TestFileEditor.php')
-rw-r--r--www/wiki/tests/parser/TestFileEditor.php196
1 files changed, 196 insertions, 0 deletions
diff --git a/www/wiki/tests/parser/TestFileEditor.php b/www/wiki/tests/parser/TestFileEditor.php
new file mode 100644
index 00000000..1bee31ea
--- /dev/null
+++ b/www/wiki/tests/parser/TestFileEditor.php
@@ -0,0 +1,196 @@
+<?php
+
+class TestFileEditor {
+ private $lines;
+ private $numLines;
+ private $deletions;
+ private $changes;
+ private $pos;
+ private $warningCallback;
+ private $result;
+
+ public static function edit( $text, array $deletions, array $changes, $warningCallback = null ) {
+ $editor = new self( $text, $deletions, $changes, $warningCallback );
+ $editor->execute();
+ return $editor->result;
+ }
+
+ private function __construct( $text, array $deletions, array $changes, $warningCallback ) {
+ $this->lines = explode( "\n", $text );
+ $this->numLines = count( $this->lines );
+ $this->deletions = array_flip( $deletions );
+ $this->changes = $changes;
+ $this->pos = 0;
+ $this->warningCallback = $warningCallback;
+ $this->result = '';
+ }
+
+ private function execute() {
+ while ( $this->pos < $this->numLines ) {
+ $line = $this->lines[$this->pos];
+ switch ( $this->getHeading( $line ) ) {
+ case 'test':
+ $this->parseTest();
+ break;
+ case 'hooks':
+ case 'functionhooks':
+ case 'transparenthooks':
+ $this->parseHooks();
+ break;
+ default:
+ if ( $this->pos < $this->numLines - 1 ) {
+ $line .= "\n";
+ }
+ $this->emitComment( $line );
+ $this->pos++;
+ }
+ }
+ foreach ( $this->deletions as $deletion => $unused ) {
+ $this->warning( "Could not find test \"$deletion\" to delete it" );
+ }
+ foreach ( $this->changes as $test => $sectionChanges ) {
+ foreach ( $sectionChanges as $section => $change ) {
+ $this->warning( "Could not find section \"$section\" in test \"$test\" " .
+ "to {$change['op']} it" );
+ }
+ }
+ }
+
+ private function warning( $text ) {
+ $cb = $this->warningCallback;
+ if ( $cb ) {
+ $cb( $text );
+ }
+ }
+
+ private function getHeading( $line ) {
+ if ( preg_match( '/^!!\s*(\S+)/', $line, $m ) ) {
+ return $m[1];
+ } else {
+ return false;
+ }
+ }
+
+ private function parseTest() {
+ $test = [];
+ $line = $this->lines[$this->pos++];
+ $heading = $this->getHeading( $line );
+ $section = [
+ 'name' => $heading,
+ 'headingLine' => $line,
+ 'contents' => ''
+ ];
+
+ while ( $this->pos < $this->numLines ) {
+ $line = $this->lines[$this->pos++];
+ $nextHeading = $this->getHeading( $line );
+ if ( $nextHeading === 'end' ) {
+ $test[] = $section;
+
+ // Add trailing line breaks to the "end" section, to allow for neat deletions
+ $trail = '';
+ for ( $i = 0; $i < $this->numLines - $this->pos - 1; $i++ ) {
+ if ( $this->lines[$this->pos + $i] === '' ) {
+ $trail .= "\n";
+ } else {
+ break;
+ }
+ }
+ $this->pos += strlen( $trail );
+
+ $test[] = [
+ 'name' => 'end',
+ 'headingLine' => $line,
+ 'contents' => $trail
+ ];
+ $this->emitTest( $test );
+ return;
+ } elseif ( $nextHeading !== false ) {
+ $test[] = $section;
+ $heading = $nextHeading;
+ $section = [
+ 'name' => $heading,
+ 'headingLine' => $line,
+ 'contents' => ''
+ ];
+ } else {
+ $section['contents'] .= "$line\n";
+ }
+ }
+
+ throw new Exception( 'Unexpected end of file' );
+ }
+
+ private function parseHooks() {
+ $line = $this->lines[$this->pos++];
+ $heading = $this->getHeading( $line );
+ $expectedEnd = 'end' . $heading;
+ $contents = "$line\n";
+
+ do {
+ $line = $this->lines[$this->pos++];
+ $nextHeading = $this->getHeading( $line );
+ $contents .= "$line\n";
+ } while ( $this->pos < $this->numLines && $nextHeading !== $expectedEnd );
+
+ if ( $nextHeading !== $expectedEnd ) {
+ throw new Exception( 'Unexpected end of file' );
+ }
+ $this->emitHooks( $heading, $contents );
+ }
+
+ protected function emitComment( $contents ) {
+ $this->result .= $contents;
+ }
+
+ protected function emitTest( $test ) {
+ $testName = false;
+ foreach ( $test as $section ) {
+ if ( $section['name'] === 'test' ) {
+ $testName = rtrim( $section['contents'], "\n" );
+ }
+ }
+ if ( isset( $this->deletions[$testName] ) ) {
+ // Acknowledge deletion
+ unset( $this->deletions[$testName] );
+ return;
+ }
+ if ( isset( $this->changes[$testName] ) ) {
+ $changes =& $this->changes[$testName];
+ foreach ( $test as $i => $section ) {
+ $sectionName = $section['name'];
+ if ( isset( $changes[$sectionName] ) ) {
+ $change = $changes[$sectionName];
+ switch ( $change['op'] ) {
+ case 'rename':
+ $test[$i]['name'] = $change['value'];
+ $test[$i]['headingLine'] = "!! {$change['value']}";
+ break;
+ case 'update':
+ $test[$i]['contents'] = $change['value'];
+ break;
+ case 'delete':
+ $test[$i]['deleted'] = true;
+ break;
+ default:
+ throw new Exception( "Unknown op: ${change['op']}" );
+ }
+ // Acknowledge
+ // Note that we use the old section name for the rename op
+ unset( $changes[$sectionName] );
+ }
+ }
+ }
+ foreach ( $test as $section ) {
+ if ( isset( $section['deleted'] ) ) {
+ continue;
+ }
+ $this->result .= $section['headingLine'] . "\n";
+ $this->result .= $section['contents'];
+ }
+ }
+
+ protected function emitHooks( $heading, $contents ) {
+ $this->result .= $contents;
+ }
+}