setName('check:sintax') ->setDescription('Check sintax for those characters that need open and close') ->addOption('dir', '', InputOption::VALUE_OPTIONAL, 'Directory to read all yaml files in it.', '') ->addOption('yml', '', InputOption::VALUE_OPTIONAL, 'Path to read yaml file.', '') ->addOption('string', '', InputOption::VALUE_OPTIONAL, 'String to check.', '') ->addOption('debug', '', InputOption::VALUE_OPTIONAL, 'Debug mode.', false) ->setHelp('Check sintax for those characters that need open and close'); } /** * @param InputInterface $input * @param OutputInterface $output * @return int Return 0 if OK, otherwise 1 */ protected function execute(InputInterface $input, OutputInterface $output) { $dir = $input->getOption("dir"); $fileYaml = $input->getOption("yml"); $string = $input->getOption("string"); $this->debug = $input->getOption("debug"); $this->loadParameters(); $error = "Nothing to do."; if (trim($fileYaml) != "") { $output->writeln("Checking sintax " . $fileYaml); $data = Yaml::parse(file_get_contents($fileYaml)); $error = $this->checkFileArray($data); } elseif (strlen(trim($dir)) > 0) { $error = $this->checkPathFile($output, $dir); } elseif (strlen(trim($string)) > 0) { $error = $this->verifyString($string); } if (is_null($error)) { $this->returnOK($output); return 0; } else { $output->writeln("CHECK SINTAX ERROR!!!"); $output->writeln($error); } return 1; } /** * Load parameters form file. */ private function loadParameters() { $fileLocator = $this->getContainer()->get('file_locator'); $templates = $fileLocator->locate('@CheckSintaxBundle/Resources/templates.txt'); $lines = explode(PHP_EOL, file_get_contents($templates)); foreach ($lines as $l) { $ch = explode(":", trim($l)); if (count($ch) === 2) { $this->chars[] = new CheckChars($ch[0], $ch[1]); } } } /** * Write string ok. * @param OutputInterface $output */ private function returnOK(OutputInterface $output) { $output->writeln("CHECK SINTAX OK!!!"); } /** * Write error found. */ private function checkCharsArrayErrors() { $error = null; foreach ($this->chars as $cc) { if ($cc->getCounter() != 0) { if ($cc->getCounter() > 0) { $error = "There is more character " . $cc->getOpen() . " open (" . $cc->getCounter() . ") than character " . $cc->getClose() . " close."; } else { $error = "There is more character " . $cc->getClose() . " close (" . ($cc->getCounter() * -1) . ") than character " . $cc->getOpen() . " open."; } } } return $error; } /** * Reset de counters chars. */ private function resetCounters() { foreach ($this->chars as $cc) { $cc->resetCounter(); } } /** * @param OutputInterface $output * @param string $dir Contains directory to read all files in it. * @return string|null Return error. */ private function checkPathFile(OutputInterface $output, $dir) { $files = scandir($dir); $error = null; foreach ($files as $file) { if (is_file($dir . $file)) { $output->writeln("Checking sintax " . $dir . $file); $errorTmp = $this->checkFileArray(Yaml::parse(file_get_contents($dir . $file))); if (!is_null($errorTmp) && strlen(trim($errorTmp)) > 0) { if (is_null($error)) { $error = "\n$dir$file"; } else { $error = "$error\n--------------------------------------------------------------------------------\n$dir$file"; } $error = "$error\n$errorTmp\n"; } } } return $error; } /** * @param array $data Contains array with data to check. * @param string $errorKey Contains key with error. * @return string|null Return error or null if OK! */ private function checkFileArray($data, $errorKey = "") { $error = null; foreach ($data as $key => $value) { if (is_array($value)) { $error .= $this->checkFileArray($value, $errorKey . ($errorKey == "" ? "" : " -> ") . $key); } else { $this->checkString($value); $errorTmp = $this->checkCharsArrayErrors(); if (!is_null($errorTmp)) { $errorTmp .= "\nError in $errorKey -> $key."; if ($this->debug) { $errorTmp .= "\n$value"; } $errorTmp .= "\n################################################################################"; } if (!is_null($errorTmp)) { if (is_null($error)) { $error = $errorTmp; } else { $error = "$error\n$errorTmp\n"; } $this->resetCounters(); } } } return $error; } //-------------- //--- STRING --- //-------------- /** * @param string $string Contains script bash. */ private function checkString($string) { for ($i = 0, $size = (strlen($string) - 1); $i < $size;) { $pos = $this->checkCharsArray($string[$i], $string[$i + 1]); if ($pos == 0) { $i++; } else { $i = $i + $pos; } } if ($i <= $size) { $this->checkCharsArray($string[$size], ""); } } /** * @param string $ch Contians char to check. * @param string $chplus Contains char +1. * @return int Return position to advance. */ private function checkCharsArray($ch, $chplus) { $pos = 0; foreach ($this->chars as $cc) { $pos = $cc->check($ch, $chplus); if ($pos > 0) { return $pos; } } return $pos; } /** * @param string $string Contians string to check * @return null|string Return the $error value. If null it ok!!! */ private function verifyString($string) { $error = null; $string = trim($string); if ($this->isJson($string)) { $string = str_replace("\\r\\n", "", $string); if ($this->isValidJson($string)) { return null; } else { $error = $this->errorJson(); } } else { $this->checkString($string); $error = $this->checkCharsArrayErrors(); } return $error; } //------------ //--- JSON --- //------------ /** * @return string Return json error. */ private function errorJson() { switch (json_last_error()) { case JSON_ERROR_DEPTH: $error = 'The maximum stack depth has been exceeded.'; break; case JSON_ERROR_STATE_MISMATCH: $error = 'Invalid or malformed JSON.'; break; case JSON_ERROR_CTRL_CHAR: $error = 'Control character error, possibly incorrectly encoded.'; break; case JSON_ERROR_SYNTAX: $error = 'Syntax error, malformed JSON.'; break; // PHP >= 5.3.3 case JSON_ERROR_UTF8: $error = 'Malformed UTF-8 characters, possibly incorrectly encoded.'; break; // PHP >= 5.5.0 case JSON_ERROR_RECURSION: $error = 'One or more recursive references in the value to be encoded.'; break; // PHP >= 5.5.0 case JSON_ERROR_INF_OR_NAN: $error = 'One or more NAN or INF values in the value to be encoded.'; break; case JSON_ERROR_UNSUPPORTED_TYPE: $error = 'A value of a type that cannot be encoded was given.'; break; default: $error = 'Unknown JSON error occured.'; break; } return "\n" . $error . "\n" . json_last_error_msg(); } /** * @param string $string Json as string. * @return bool Return TRUE if is a valid json. */ private function isJson($string) { if (substr($string, 0, 1) == "{" && substr($string, strlen($string) - 1, strlen($string)) == "}") { for ($i = 1, $size = (strlen($string) - 1); $i < $size; $i++) { if (strlen(trim($string[$i])) != 0) { // es el primer caracter despues de la llave de inicio, entonces tiene que ser una comilla doble return $string[$i] == '"'; } } } else { return false; } return false; } /** * @param string $string Json as string. * @return bool Return TRUE if is a valid json. */ private function isValidJson($string) { json_decode($string); return (json_last_error() == JSON_ERROR_NONE); } } class CheckChars { /** * @var string Contains open char. */ private $open; /** * @var string Contains close char. */ private $close; /** * @var string Contains the number of match. If open match then +1 if close match then -1. */ private $counter; /** * @var boolean Tell if open and close are equals. */ private $equals; /** * OneChar constructor. * @param $open * @param $close */ public function __construct($open, $close) { $this->open = $open; $this->close = $close; $this->equals = $open == $close; $this->counter = 0; } /** * @param string $ch Contians char to check. * @param string $chplus Contains char +1. * @return int Return position to advance. */ public function check($ch, $chplus) { // primero chequeo si estoy en una dupla, si es asi, entonces no realizo el chequeo de simple $chplus = $ch . $chplus; $pos = 0; if ($chplus == $this->getOpen()) { $this->foundOpen(); $pos = 2; } elseif ($chplus == $this->getClose()) { $this->foundClose(); $pos = 2; } elseif ($ch == $this->getOpen()) { $this->foundOpen(); $pos = 1; } elseif ($ch == $this->getClose()) { $this->foundClose(); $pos = 1; } return $pos; } /** * @return mixed */ public function getOpen() { return $this->open; } /** * @param mixed $open */ public function setOpen($open) { $this->open = $open; } /** * @return mixed */ public function getClose() { return $this->close; } /** * @param mixed $close */ public function setClose($close) { $this->close = $close; } /** * Add 1 to counter */ public function foundOpen() { $this->counter++; } /** * Sub 1 to counter */ public function foundClose() { $this->counter--; } /** * Reset counter */ public function resetCounter() { $this->counter = 0; } /** * @return string */ public function getCounter() { if ($this->equals) { // si los valores de open y close con iguales y el counter es par, entonces es correcta la cantidad de llaves return $this->counter % 2 == 0 ? 0 : $this->counter; } else { return $this->counter; } } }