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