Merge "Change php extract() to explicit code"
[lhc/web/wiklou.git] / tests / parser / TestFileEditor.php
1 <?php
2
3 class TestFileEditor {
4 private $lines;
5 private $numLines;
6 private $deletions;
7 private $changes;
8 private $pos;
9 private $warningCallback;
10 private $result;
11
12 public static function edit( $text, array $deletions, array $changes, $warningCallback = null ) {
13 $editor = new self( $text, $deletions, $changes, $warningCallback );
14 $editor->execute();
15 return $editor->result;
16 }
17
18 private function __construct( $text, array $deletions, array $changes, $warningCallback ) {
19 $this->lines = explode( "\n", $text );
20 $this->numLines = count( $this->lines );
21 $this->deletions = array_flip( $deletions );
22 $this->changes = $changes;
23 $this->pos = 0;
24 $this->warningCallback = $warningCallback;
25 $this->result = '';
26 }
27
28 private function execute() {
29 while ( $this->pos < $this->numLines ) {
30 $line = $this->lines[$this->pos];
31 switch ( $this->getHeading( $line ) ) {
32 case 'test':
33 $this->parseTest();
34 break;
35 case 'hooks':
36 case 'functionhooks':
37 case 'transparenthooks':
38 $this->parseHooks();
39 break;
40 default:
41 if ( $this->pos < $this->numLines - 1 ) {
42 $line .= "\n";
43 }
44 $this->emitComment( $line );
45 $this->pos++;
46 }
47 }
48 foreach ( $this->deletions as $deletion => $unused ) {
49 $this->warning( "Could not find test \"$deletion\" to delete it" );
50 }
51 foreach ( $this->changes as $test => $sectionChanges ) {
52 foreach ( $sectionChanges as $section => $change ) {
53 $this->warning( "Could not find section \"$section\" in test \"$test\" " .
54 "to {$change['op']} it" );
55 }
56 }
57 }
58
59 private function warning( $text ) {
60 $cb = $this->warningCallback;
61 if ( $cb ) {
62 $cb( $text );
63 }
64 }
65
66 private function getHeading( $line ) {
67 if ( preg_match( '/^!!\s*(\S+)/', $line, $m ) ) {
68 return $m[1];
69 } else {
70 return false;
71 }
72 }
73
74 private function parseTest() {
75 $test = [];
76 $line = $this->lines[$this->pos++];
77 $heading = $this->getHeading( $line );
78 $section = [
79 'name' => $heading,
80 'headingLine' => $line,
81 'contents' => ''
82 ];
83
84 while ( $this->pos < $this->numLines ) {
85 $line = $this->lines[$this->pos++];
86 $nextHeading = $this->getHeading( $line );
87 if ( $nextHeading === 'end' ) {
88 $test[] = $section;
89
90 // Add trailing line breaks to the "end" section, to allow for neat deletions
91 $trail = '';
92 for ( $i = 0; $i < $this->numLines - $this->pos - 1; $i++ ) {
93 if ( $this->lines[$this->pos + $i] === '' ) {
94 $trail .= "\n";
95 } else {
96 break;
97 }
98 }
99 $this->pos += strlen( $trail );
100
101 $test[] = [
102 'name' => 'end',
103 'headingLine' => $line,
104 'contents' => $trail
105 ];
106 $this->emitTest( $test );
107 return;
108 } elseif ( $nextHeading !== false ) {
109 $test[] = $section;
110 $heading = $nextHeading;
111 $section = [
112 'name' => $heading,
113 'headingLine' => $line,
114 'contents' => ''
115 ];
116 } else {
117 $section['contents'] .= "$line\n";
118 }
119 }
120
121 throw new Exception( 'Unexpected end of file' );
122 }
123
124 private function parseHooks() {
125 $line = $this->lines[$this->pos++];
126 $heading = $this->getHeading( $line );
127 $expectedEnd = 'end' . $heading;
128 $contents = "$line\n";
129
130 do {
131 $line = $this->lines[$this->pos++];
132 $nextHeading = $this->getHeading( $line );
133 $contents .= "$line\n";
134 } while ( $this->pos < $this->numLines && $nextHeading !== $expectedEnd );
135
136 if ( $nextHeading !== $expectedEnd ) {
137 throw new Exception( 'Unexpected end of file' );
138 }
139 $this->emitHooks( $heading, $contents );
140 }
141
142 protected function emitComment( $contents ) {
143 $this->result .= $contents;
144 }
145
146 protected function emitTest( $test ) {
147 $testName = false;
148 foreach ( $test as $section ) {
149 if ( $section['name'] === 'test' ) {
150 $testName = rtrim( $section['contents'], "\n" );
151 }
152 }
153 if ( isset( $this->deletions[$testName] ) ) {
154 // Acknowledge deletion
155 unset( $this->deletions[$testName] );
156 return;
157 }
158 if ( isset( $this->changes[$testName] ) ) {
159 $changes =& $this->changes[$testName];
160 foreach ( $test as $i => $section ) {
161 $sectionName = $section['name'];
162 if ( isset( $changes[$sectionName] ) ) {
163 $change = $changes[$sectionName];
164 switch ( $change['op'] ) {
165 case 'rename':
166 $test[$i]['name'] = $change['value'];
167 $test[$i]['headingLine'] = "!! {$change['value']}";
168 break;
169 case 'update':
170 $test[$i]['contents'] = $change['value'];
171 break;
172 case 'delete':
173 $test[$i]['deleted'] = true;
174 break;
175 default:
176 throw new Exception( "Unknown op: ${change['op']}" );
177 }
178 // Acknowledge
179 // Note that we use the old section name for the rename op
180 unset( $changes[$sectionName] );
181 }
182 }
183 }
184 foreach ( $test as $section ) {
185 if ( isset( $section['deleted'] ) ) {
186 continue;
187 }
188 $this->result .= $section['headingLine'] . "\n";
189 $this->result .= $section['contents'];
190 }
191 }
192
193 protected function emitHooks( $heading, $contents ) {
194 $this->result .= $contents;
195 }
196 }