Report correct position for SQL files with unicode characters
[php-sqllint.git] / src / phpsqllint / Cli.php
1 <?php
2 /**
3  * Part of php-sqllint
4  *
5  * PHP version 5
6  *
7  * @category Tools
8  * @package  PHP-SQLlint
9  * @author   Christian Weiske <cweiske@cweiske.de>
10  * @license  http://www.gnu.org/licenses/agpl.html GNU AGPL v3
11  * @link     http://cweiske.de/php-sqllint.htm
12  */
13 namespace phpsqllint;
14 use PhpMyAdmin\SqlParser\Parser;
15
16 /**
17  * Command line interface
18  *
19  * @category Tools
20  * @package  PHP-SQLlint
21  * @author   Christian Weiske <cweiske@cweiske.de>
22  * @license  http://www.gnu.org/licenses/agpl.html GNU AGPL v3
23  * @link     http://www.emacswiki.org/emacs/CreatingYourOwnCompileErrorRegexp
24  */
25 class Cli
26 {
27     protected $renderer;
28
29     protected $format = false;
30
31     /**
32      * What syntax highlighting mode should be used
33      *
34      * none, ansi, html
35      */
36     protected $highlight = 'none';
37
38
39     /**
40      * Start processing.
41      *
42      * @return void
43      */
44     public function run()
45     {
46         try {
47             $parser = $this->loadOptionParser();
48             $files  = $this->parseParameters($parser);
49
50             $allfine = true;
51             foreach ($files as $filename) {
52                 if ($this->format) {
53                     $allfine &= $this->formatFile($filename);
54                 } else {
55                     $allfine &= $this->checkFile($filename);
56                 }
57             }
58
59             if ($allfine == true) {
60                 exit(0);
61             } else {
62                 exit(10);
63             }
64         } catch (\Exception $e) {
65             echo 'Error: ' . $e->getMessage() . "\n";
66             exit(1);
67         }
68     }
69
70     /**
71      * Check a .sql file for syntax errors
72      *
73      * @param string $filename File path
74      *
75      * @return boolean True if there were no errors, false if there were some
76      */
77     public function checkFile($filename)
78     {
79         $this->renderer->startRendering($filename);
80         $sql = $this->loadSql($filename);
81         if ($sql === false) {
82             return false;
83         }
84
85         $parser = new \PhpMyAdmin\SqlParser\Parser($sql);
86         if (count($parser->errors) == 0) {
87             $this->renderer->finishOk();
88             return true;
89         }
90
91         $lines = array(1 => 0);
92         $pos = -1;
93         $line = 1;
94         while (false !== $pos = mb_strpos($sql, "\n", ++$pos)) {
95             $lines[++$line] = $pos;
96         }
97
98         foreach ($parser->errors as $error) {
99             /* @var PhpMyAdmin\SqlParser\Exceptions\ParserException $error) */
100             reset($lines);
101             $line = 1;
102             while (next($lines) && $error->token->position >= current($lines)) {
103                 ++$line;
104             }
105             $col = $error->token->position - $lines[$line];
106
107             $this->renderer->displayError(
108                 $error->getMessage(),
109                 //FIXME: ->token or ->value?
110                 $error->token->token,
111                 $line,
112                 $col
113             );
114         }
115
116         return false;
117     }
118
119     /**
120      * Reformat the given file
121      */
122     protected function formatFile($filename)
123     {
124         $this->renderer->startRendering($filename);
125         $sql = $this->loadSql($filename);
126         if ($sql === false) {
127             return false;
128         }
129
130         $typeMap = array(
131             'none' => 'text',
132             'ansi' => 'cli',
133             'html' => 'html',
134         );
135         $options = array(
136             'type' => $typeMap[$this->highlight],
137         );
138         echo \PhpMyAdmin\SqlParser\Utils\Formatter::format($sql, $options) . "\n";
139     }
140
141     protected function loadSql($filename)
142     {
143         if ($filename == '-') {
144             $sql = file_get_contents('php://stdin');
145         } else {
146             $sql = file_get_contents($filename);
147         }
148         if (trim($sql) == '') {
149             $this->renderer->displayError('SQL file empty', '', 0, 0);
150             return false;
151         }
152         return $sql;
153     }
154
155     /**
156      * Load parameters for the CLI option parser.
157      *
158      * @return \Console_CommandLine CLI option parser
159      */
160     protected function loadOptionParser()
161     {
162         $parser = new \Console_CommandLine();
163         $parser->description = 'php-sqllint';
164         $parser->version = 'dev';
165         $parser->avoid_reading_stdin = true;
166
167         $versionFile = __DIR__ . '/../../VERSION';
168         if (file_exists($versionFile)) {
169             $parser->version = trim(file_get_contents($versionFile));
170         }
171
172         $parser->addOption(
173             'format',
174             array(
175                 'short_name'  => '-f',
176                 'long_name'   => '--format',
177                 'description' => 'Reformat SQL instead of checking',
178                 'action'      => 'StoreTrue',
179                 'default'     => false,
180             )
181         );
182         $parser->addOption(
183             'highlight',
184             array(
185                 'short_name'  => '-h',
186                 'long_name'   => '--highlight',
187                 'description' => 'Highlighting mode (when using --format)',
188                 'action'      => 'StoreString',
189                 'choices'     => array(
190                     'none',
191                     'ansi',
192                     'html',
193                     'auto',
194                 ),
195                 'default' => 'auto',
196                 'add_list_option' => true,
197             )
198         );
199         $parser->addOption(
200             'renderer',
201             array(
202                 'short_name'  => '-r',
203                 'long_name'   => '--renderer',
204                 'description' => 'Output mode',
205                 'action'      => 'StoreString',
206                 'choices'     => array(
207                     'emacs',
208                     'text',
209                 ),
210                 'default'     => 'text',
211                 'add_list_option' => true,
212             )
213         );
214
215         $parser->addArgument(
216             'sql_files',
217             array(
218                 'description' => 'SQL files, "-" for stdin',
219                 'multiple'    => true
220             )
221         );
222
223         return $parser;
224     }
225
226     /**
227      * Let the CLI option parser parse the options.
228      *
229      * @param object $parser Option parser
230      *
231      * @return array Array of file names
232      */
233     protected function parseParameters(\Console_CommandLine $parser)
234     {
235         try {
236             $result = $parser->parse();
237
238             $rendClass = '\\phpsqllint\\Renderer_'
239                 . ucfirst($result->options['renderer']);
240             $this->renderer = new $rendClass();
241
242             $this->format = $result->options['format'];
243
244             $this->highlight = $result->options['highlight'];
245             if ($this->highlight == 'auto') {
246                 if (php_sapi_name() == 'cli') {
247                     //default coloring to enabled, except
248                     // when piping | to another tool
249                     $this->highlight = 'ansi';
250                     if (function_exists('posix_isatty')
251                         && !posix_isatty(STDOUT)
252                     ) {
253                         $this->highlight = 'none';
254                     }
255                 } else {
256                     //no idea where we are, so do not highlight
257                     $this->highlight = 'none';
258                 }
259             }
260
261             foreach ($result->args['sql_files'] as $filename) {
262                 if ($filename == '-') {
263                     continue;
264                 }
265                 if (!file_exists($filename)) {
266                     throw new \Exception('File does not exist: ' . $filename);
267                 }
268                 if (!is_file($filename)) {
269                     throw new \Exception('Not a file: ' . $filename);
270                 }
271             }
272
273             return $result->args['sql_files'];
274         } catch (\Exception $exc) {
275             $parser->displayError($exc->getMessage());
276         }
277     }
278
279 }
280 ?>