CheckSintaxCommand.php 12 KB

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