|
@@ -0,0 +1,432 @@
|
|
|
+<?php
|
|
|
+
|
|
|
+namespace CheckSintaxBundle\Command;
|
|
|
+
|
|
|
+use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
|
|
|
+use Symfony\Component\Console\Input\InputInterface;
|
|
|
+use Symfony\Component\Console\Input\InputOption;
|
|
|
+use Symfony\Component\Console\Output\OutputInterface;
|
|
|
+use Symfony\Component\Yaml\Yaml;
|
|
|
+
|
|
|
+class CheckSintaxCommand extends ContainerAwareCommand
|
|
|
+{
|
|
|
+ /**
|
|
|
+ * @var array|CheckChars
|
|
|
+ */
|
|
|
+ private $chars = [];
|
|
|
+ private $debug;
|
|
|
+
|
|
|
+ protected function configure()
|
|
|
+ {
|
|
|
+ $this
|
|
|
+ ->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) != "") {
|
|
|
+ $data = Yaml::parse(file_get_contents($fileYaml));
|
|
|
+ $error = $this->checkFileArray($data);
|
|
|
+ } elseif (strlen(trim($dir)) > 0) {
|
|
|
+ $error = $this->checkPathFile($dir);
|
|
|
+ } elseif (strlen(trim($string)) > 0) {
|
|
|
+ $error = $this->verifyString($string);
|
|
|
+ }
|
|
|
+ if (is_null($error)) {
|
|
|
+ $this->returnOK($output);
|
|
|
+ return 0;
|
|
|
+ } else {
|
|
|
+ $output->writeln($error);
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 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("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 string $dir Contains directory to read all files in it.
|
|
|
+ * @return string|null Return error.
|
|
|
+ */
|
|
|
+ private function checkPathFile($dir)
|
|
|
+ {
|
|
|
+ $files = scandir($dir);
|
|
|
+ $error = null;
|
|
|
+ foreach ($files as $file) {
|
|
|
+ if (is_file($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;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|