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