CheckSintaxCommand.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433
  1. <?php
  2. namespace CheckSintaxBundle\Command;
  3. use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
  4. use Symfony\Component\Console\Input\InputInterface;
  5. use Symfony\Component\Console\Input\InputOption;
  6. use Symfony\Component\Console\Output\OutputInterface;
  7. use Symfony\Component\Yaml\Yaml;
  8. class CheckSintaxCommand extends ContainerAwareCommand
  9. {
  10. /**
  11. * @var array|CheckChars
  12. */
  13. private $chars = [];
  14. private $debug;
  15. protected function configure()
  16. {
  17. $this
  18. ->setName('check:sintax')
  19. ->setDescription('Check sintax for those characters that need open and close')
  20. ->addOption('dir', '', InputOption::VALUE_OPTIONAL, 'Directory to read all yaml files in it.', '')
  21. ->addOption('yml', '', InputOption::VALUE_OPTIONAL, 'Path to read yaml file.', '')
  22. ->addOption('string', '', InputOption::VALUE_OPTIONAL, 'String to check.', '')
  23. ->addOption('debug', '', InputOption::VALUE_OPTIONAL, 'Debug mode.', false)
  24. ->setHelp('Check sintax for those characters that need open and close');
  25. }
  26. /**
  27. * @param InputInterface $input
  28. * @param OutputInterface $output
  29. * @return int Return 0 if OK, otherwise 1
  30. */
  31. protected function execute(InputInterface $input, OutputInterface $output)
  32. {
  33. $dir = $input->getOption("dir");
  34. $fileYaml = $input->getOption("yml");
  35. $string = $input->getOption("string");
  36. $this->debug = $input->getOption("debug");
  37. $this->loadParameters();
  38. $error = "Nothing to do.";
  39. if (trim($fileYaml) != "") {
  40. $data = Yaml::parse(file_get_contents($fileYaml));
  41. $error = $this->checkFileArray($data);
  42. } elseif (strlen(trim($dir)) > 0) {
  43. $error = $this->checkPathFile($dir);
  44. } elseif (strlen(trim($string)) > 0) {
  45. $error = $this->verifyString($string);
  46. }
  47. if (is_null($error)) {
  48. $this->returnOK($output);
  49. return 0;
  50. } else {
  51. $output->writeln("CHECK SINTAX ERROR!!!");
  52. $output->writeln($error);
  53. }
  54. return 1;
  55. }
  56. /**
  57. * Load parameters form file.
  58. */
  59. private function loadParameters()
  60. {
  61. $fileLocator = $this->getContainer()->get('file_locator');
  62. $templates = $fileLocator->locate('@CheckSintaxBundle/Resources/templates.txt');
  63. $lines = explode(PHP_EOL, file_get_contents($templates));
  64. foreach ($lines as $l) {
  65. $ch = explode(":", trim($l));
  66. if (count($ch) === 2) {
  67. $this->chars[] = new CheckChars($ch[0], $ch[1]);
  68. }
  69. }
  70. }
  71. /**
  72. * Write string ok.
  73. * @param OutputInterface $output
  74. */
  75. private function returnOK(OutputInterface $output)
  76. {
  77. $output->writeln("CHECK SINTAX OK!!!");
  78. }
  79. /**
  80. * Write error found.
  81. */
  82. private function checkCharsArrayErrors()
  83. {
  84. $error = null;
  85. foreach ($this->chars as $cc) {
  86. if ($cc->getCounter() != 0) {
  87. if ($cc->getCounter() > 0) {
  88. $error = "There is more character " . $cc->getOpen() . " open (" . $cc->getCounter() . ") than character " . $cc->getClose() . " close.";
  89. } else {
  90. $error = "There is more character " . $cc->getClose() . " close (" . ($cc->getCounter() * -1) . ") than character " . $cc->getOpen() . " open.";
  91. }
  92. }
  93. }
  94. return $error;
  95. }
  96. /**
  97. * Reset de counters chars.
  98. */
  99. private function resetCounters()
  100. {
  101. foreach ($this->chars as $cc) {
  102. $cc->resetCounter();
  103. }
  104. }
  105. /**
  106. * @param string $dir Contains directory to read all files in it.
  107. * @return string|null Return error.
  108. */
  109. private function checkPathFile($dir)
  110. {
  111. $files = scandir($dir);
  112. $error = null;
  113. foreach ($files as $file) {
  114. if (is_file($dir . $file)) {
  115. $errorTmp = $this->checkFileArray(Yaml::parse(file_get_contents($dir . $file)));
  116. if (!is_null($errorTmp) && strlen(trim($errorTmp)) > 0) {
  117. if (is_null($error)) {
  118. $error = "\n$dir$file";
  119. } else {
  120. $error = "$error\n--------------------------------------------------------------------------------\n$dir$file";
  121. }
  122. $error = "$error\n$errorTmp\n";
  123. }
  124. }
  125. }
  126. return $error;
  127. }
  128. /**
  129. * @param array $data Contains array with data to check.
  130. * @param string $errorKey Contains key with error.
  131. * @return string|null Return error or null if OK!
  132. */
  133. private function checkFileArray($data, $errorKey = "")
  134. {
  135. $error = null;
  136. foreach ($data as $key => $value) {
  137. if (is_array($value)) {
  138. $error .= $this->checkFileArray($value, $errorKey . ($errorKey == "" ? "" : " -> ") . $key);
  139. } else {
  140. $this->checkString($value);
  141. $errorTmp = $this->checkCharsArrayErrors();
  142. if (!is_null($errorTmp)) {
  143. $errorTmp .= "\nError in $errorKey -> $key.";
  144. if ($this->debug) {
  145. $errorTmp .= "\n$value";
  146. }
  147. $errorTmp .= "\n################################################################################";
  148. }
  149. if (!is_null($errorTmp)) {
  150. if (is_null($error)) {
  151. $error = $errorTmp;
  152. } else {
  153. $error = "$error\n$errorTmp\n";
  154. }
  155. $this->resetCounters();
  156. }
  157. }
  158. }
  159. return $error;
  160. }
  161. //--------------
  162. //--- STRING ---
  163. //--------------
  164. /**
  165. * @param string $string Contains script bash.
  166. */
  167. private function checkString($string)
  168. {
  169. for ($i = 0, $size = (strlen($string) - 1); $i < $size;) {
  170. $pos = $this->checkCharsArray($string[$i], $string[$i + 1]);
  171. if ($pos == 0) {
  172. $i++;
  173. } else {
  174. $i = $i + $pos;
  175. }
  176. }
  177. if ($i <= $size) {
  178. $this->checkCharsArray($string[$size], "");
  179. }
  180. }
  181. /**
  182. * @param string $ch Contians char to check.
  183. * @param string $chplus Contains char +1.
  184. * @return int Return position to advance.
  185. */
  186. private function checkCharsArray($ch, $chplus)
  187. {
  188. $pos = 0;
  189. foreach ($this->chars as $cc) {
  190. $pos = $cc->check($ch, $chplus);
  191. if ($pos > 0) {
  192. return $pos;
  193. }
  194. }
  195. return $pos;
  196. }
  197. /**
  198. * @param string $string Contians string to check
  199. * @return null|string Return the $error value. If null it ok!!!
  200. */
  201. private function verifyString($string)
  202. {
  203. $error = null;
  204. $string = trim($string);
  205. if ($this->isJson($string)) {
  206. $string = str_replace("\\r\\n", "", $string);
  207. if ($this->isValidJson($string)) {
  208. return null;
  209. } else {
  210. $error = $this->errorJson();
  211. }
  212. } else {
  213. $this->checkString($string);
  214. $error = $this->checkCharsArrayErrors();
  215. }
  216. return $error;
  217. }
  218. //------------
  219. //--- JSON ---
  220. //------------
  221. /**
  222. * @return string Return json error.
  223. */
  224. private function errorJson()
  225. {
  226. switch (json_last_error()) {
  227. case JSON_ERROR_DEPTH:
  228. $error = 'The maximum stack depth has been exceeded.';
  229. break;
  230. case JSON_ERROR_STATE_MISMATCH:
  231. $error = 'Invalid or malformed JSON.';
  232. break;
  233. case JSON_ERROR_CTRL_CHAR:
  234. $error = 'Control character error, possibly incorrectly encoded.';
  235. break;
  236. case JSON_ERROR_SYNTAX:
  237. $error = 'Syntax error, malformed JSON.';
  238. break;
  239. // PHP >= 5.3.3
  240. case JSON_ERROR_UTF8:
  241. $error = 'Malformed UTF-8 characters, possibly incorrectly encoded.';
  242. break;
  243. // PHP >= 5.5.0
  244. case JSON_ERROR_RECURSION:
  245. $error = 'One or more recursive references in the value to be encoded.';
  246. break;
  247. // PHP >= 5.5.0
  248. case JSON_ERROR_INF_OR_NAN:
  249. $error = 'One or more NAN or INF values in the value to be encoded.';
  250. break;
  251. case JSON_ERROR_UNSUPPORTED_TYPE:
  252. $error = 'A value of a type that cannot be encoded was given.';
  253. break;
  254. default:
  255. $error = 'Unknown JSON error occured.';
  256. break;
  257. }
  258. return "\n" . $error . "\n" . json_last_error_msg();
  259. }
  260. /**
  261. * @param string $string Json as string.
  262. * @return bool Return TRUE if is a valid json.
  263. */
  264. private function isJson($string)
  265. {
  266. if (substr($string, 0, 1) == "{" &&
  267. substr($string, strlen($string) - 1, strlen($string)) == "}") {
  268. for ($i = 1, $size = (strlen($string) - 1); $i < $size; $i++) {
  269. if (strlen(trim($string[$i])) != 0) {
  270. // es el primer caracter despues de la llave de inicio, entonces tiene que ser una comilla doble
  271. return $string[$i] == '"';
  272. }
  273. }
  274. } else {
  275. return false;
  276. }
  277. return false;
  278. }
  279. /**
  280. * @param string $string Json as string.
  281. * @return bool Return TRUE if is a valid json.
  282. */
  283. private function isValidJson($string)
  284. {
  285. json_decode($string);
  286. return (json_last_error() == JSON_ERROR_NONE);
  287. }
  288. }
  289. class CheckChars
  290. {
  291. /**
  292. * @var string Contains open char.
  293. */
  294. private $open;
  295. /**
  296. * @var string Contains close char.
  297. */
  298. private $close;
  299. /**
  300. * @var string Contains the number of match. If open match then +1 if close match then -1.
  301. */
  302. private $counter;
  303. /**
  304. * @var boolean Tell if open and close are equals.
  305. */
  306. private $equals;
  307. /**
  308. * OneChar constructor.
  309. * @param $open
  310. * @param $close
  311. */
  312. public function __construct($open, $close)
  313. {
  314. $this->open = $open;
  315. $this->close = $close;
  316. $this->equals = $open == $close;
  317. $this->counter = 0;
  318. }
  319. /**
  320. * @param string $ch Contians char to check.
  321. * @param string $chplus Contains char +1.
  322. * @return int Return position to advance.
  323. */
  324. public function check($ch, $chplus)
  325. {
  326. // primero chequeo si estoy en una dupla, si es asi, entonces no realizo el chequeo de simple
  327. $chplus = $ch . $chplus;
  328. $pos = 0;
  329. if ($chplus == $this->getOpen()) {
  330. $this->foundOpen();
  331. $pos = 2;
  332. } elseif ($chplus == $this->getClose()) {
  333. $this->foundClose();
  334. $pos = 2;
  335. } elseif ($ch == $this->getOpen()) {
  336. $this->foundOpen();
  337. $pos = 1;
  338. } elseif ($ch == $this->getClose()) {
  339. $this->foundClose();
  340. $pos = 1;
  341. }
  342. return $pos;
  343. }
  344. /**
  345. * @return mixed
  346. */
  347. public function getOpen()
  348. {
  349. return $this->open;
  350. }
  351. /**
  352. * @param mixed $open
  353. */
  354. public function setOpen($open)
  355. {
  356. $this->open = $open;
  357. }
  358. /**
  359. * @return mixed
  360. */
  361. public function getClose()
  362. {
  363. return $this->close;
  364. }
  365. /**
  366. * @param mixed $close
  367. */
  368. public function setClose($close)
  369. {
  370. $this->close = $close;
  371. }
  372. /**
  373. * Add 1 to counter
  374. */
  375. public function foundOpen()
  376. {
  377. $this->counter++;
  378. }
  379. /**
  380. * Sub 1 to counter
  381. */
  382. public function foundClose()
  383. {
  384. $this->counter--;
  385. }
  386. /**
  387. * Reset counter
  388. */
  389. public function resetCounter()
  390. {
  391. $this->counter = 0;
  392. }
  393. /**
  394. * @return string
  395. */
  396. public function getCounter()
  397. {
  398. if ($this->equals) {
  399. // si los valores de open y close con iguales y el counter es par, entonces es correcta la cantidad de llaves
  400. return $this->counter % 2 == 0 ? 0 : $this->counter;
  401. } else {
  402. return $this->counter;
  403. }
  404. }
  405. }