From 5667ae25c55d2e5041247bd86a9f92fbf29cc545 Mon Sep 17 00:00:00 2001 From: Netkas Date: Sun, 14 Aug 2022 16:48:39 -0400 Subject: [PATCH] Added Symfony\Yaml, improved installer and Makefile and updated .gitignore --- .gitignore | 1 + .idea/codeStyles/codeStyleConfig.xml | 5 + LICENSE | 15 +- Makefile | 13 +- src/autoload/autoload.php | 2 +- src/config/ncc.ini | 42 - src/config/ncc.yaml | 41 + src/installer/generate_build_files.php | 5 +- src/installer/installer | 243 +++- src/ncc/ThirdParty/Symfony/Yaml/CHANGELOG.md | 242 ++++ .../Symfony/Yaml/Command/LintCommand.php | 279 ++++ src/ncc/ThirdParty/Symfony/Yaml/Dumper.php | 138 ++ src/ncc/ThirdParty/Symfony/Yaml/Escaper.php | 95 ++ .../Symfony/Yaml/Exception/DumpException.php | 21 + .../Yaml/Exception/ExceptionInterface.php | 21 + .../Symfony/Yaml/Exception/ParseException.php | 126 ++ .../Yaml/Exception/RuntimeException.php | 21 + src/ncc/ThirdParty/Symfony/Yaml/Inline.php | 779 ++++++++++ .../IniParser => Symfony/Yaml}/LICENSE | 14 +- src/ncc/ThirdParty/Symfony/Yaml/Parser.php | 1255 +++++++++++++++++ src/ncc/ThirdParty/Symfony/Yaml/README.md | 13 + .../Symfony/Yaml/Tag/TaggedValue.php | 38 + src/ncc/ThirdParty/Symfony/Yaml/Unescaper.php | 110 ++ src/ncc/ThirdParty/Symfony/Yaml/VERSION | 1 + src/ncc/ThirdParty/Symfony/Yaml/Yaml.php | 96 ++ .../austinhyde/IniParser/IniParser.php | 282 ---- .../austinhyde/IniParser/README.markdown | 247 ---- .../ThirdParty/austinhyde/IniParser/VERSION | 1 - src/ncc/autoload.php | 1 + src/ncc/version.json | 4 +- 30 files changed, 3514 insertions(+), 637 deletions(-) create mode 100644 .idea/codeStyles/codeStyleConfig.xml delete mode 100644 src/config/ncc.ini create mode 100644 src/config/ncc.yaml create mode 100644 src/ncc/ThirdParty/Symfony/Yaml/CHANGELOG.md create mode 100644 src/ncc/ThirdParty/Symfony/Yaml/Command/LintCommand.php create mode 100644 src/ncc/ThirdParty/Symfony/Yaml/Dumper.php create mode 100644 src/ncc/ThirdParty/Symfony/Yaml/Escaper.php create mode 100644 src/ncc/ThirdParty/Symfony/Yaml/Exception/DumpException.php create mode 100644 src/ncc/ThirdParty/Symfony/Yaml/Exception/ExceptionInterface.php create mode 100644 src/ncc/ThirdParty/Symfony/Yaml/Exception/ParseException.php create mode 100644 src/ncc/ThirdParty/Symfony/Yaml/Exception/RuntimeException.php create mode 100644 src/ncc/ThirdParty/Symfony/Yaml/Inline.php rename src/ncc/ThirdParty/{austinhyde/IniParser => Symfony/Yaml}/LICENSE (82%) create mode 100644 src/ncc/ThirdParty/Symfony/Yaml/Parser.php create mode 100644 src/ncc/ThirdParty/Symfony/Yaml/README.md create mode 100644 src/ncc/ThirdParty/Symfony/Yaml/Tag/TaggedValue.php create mode 100644 src/ncc/ThirdParty/Symfony/Yaml/Unescaper.php create mode 100644 src/ncc/ThirdParty/Symfony/Yaml/VERSION create mode 100644 src/ncc/ThirdParty/Symfony/Yaml/Yaml.php delete mode 100644 src/ncc/ThirdParty/austinhyde/IniParser/IniParser.php delete mode 100644 src/ncc/ThirdParty/austinhyde/IniParser/README.markdown delete mode 100644 src/ncc/ThirdParty/austinhyde/IniParser/VERSION diff --git a/.gitignore b/.gitignore index de01f33..43e881f 100644 --- a/.gitignore +++ b/.gitignore @@ -11,5 +11,6 @@ src/ncc/ThirdParty/Symfony/polyfill-mbstring/autoload_spl.php src/ncc/ThirdParty/Symfony/Process/autoload_spl.php src/ncc/ThirdParty/Symfony/Uid/autoload_spl.php src/ncc/ThirdParty/Symfony/Filesystem/autoload_spl.php +src/ncc/ThirdParty/Symfony/Yaml/autoload_spl.php src/ncc/autoload_spl.php src/ncc/autoload.php \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..a55e7a1 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/LICENSE b/LICENSE index 88bfeb2..c5d25b7 100644 --- a/LICENSE +++ b/LICENSE @@ -181,20 +181,19 @@ THE SOFTWARE. ------------------------ -austinhyde - IniParser +Symfony - Yaml -The MIT License (MIT) -Copyright (c) 2013 Austin Hyde +Copyright (c) 2004-2022 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, @@ -202,4 +201,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. \ No newline at end of file +THE SOFTWARE. diff --git a/Makefile b/Makefile index a8fec32..1a932d4 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ autoload: make src/ncc/ThirdParty/Symfony/Process/autoload_spl.php make src/ncc/ThirdParty/Symfony/Uid/autoload_spl.php make src/ncc/ThirdParty/Symfony/Filesystem/autoload_spl.php - make src/ncc/ThirdParty/austinhyde/IniParser/autoload_spl.php + make src/ncc/ThirdParty/Symfony/Yaml/autoload_spl.php make src/ncc/autoload_spl.php cp src/autoload/autoload.php src/ncc/autoload.php @@ -34,9 +34,9 @@ src/ncc/ThirdParty/Symfony/Filesystem/autoload_spl.php: /usr/bin/env phpab --output src/ncc/ThirdParty/Symfony/Filesystem/autoload_spl.php \ src/ncc/ThirdParty/Symfony/Filesystem -src/ncc/ThirdParty/austinhyde/IniParser/autoload_spl.php: - /usr/bin/env phpab --output src/ncc/ThirdParty/austinhyde/IniParser/autoload_spl.php \ - src/ncc/ThirdParty/austinhyde/IniParser +src/ncc/ThirdParty/Symfony/Yaml/autoload_spl.php: + /usr/bin/env phpab --output src/ncc/ThirdParty/Symfony/Yaml/autoload_spl.php \ + src/ncc/ThirdParty/Symfony/Yaml src/ncc/autoload_spl.php: /usr/bin/env phpab --output src/ncc/autoload_spl.php \ @@ -52,11 +52,12 @@ src/ncc/autoload_spl.php: src/ncc/ncc.php redist: autoload - rm -rf build + rm -rf build/src mkdir build build/src cp -rf src/ncc/* build/src cp src/installer/installer build/src/INSTALL cp src/installer/ncc.sh build/src/ncc.sh + cp src/config/ncc.yaml build/src/default_config.yaml; cp src/installer/extension build/src/extension chmod +x build/src/INSTALL cp LICENSE build/src/LICENSE @@ -76,4 +77,4 @@ clean: rm -f src/ncc/ThirdParty/Symfony/Process/autoload_spl.php rm -f src/ncc/ThirdParty/Symfony/Uid/autoload_spl.php rm -f src/ncc/ThirdParty/Symfony/Filesystem/autoload_spl.php - rm -f src/ncc/ThirdParty/austinhyde/IniParser/autoload_spl.php \ No newline at end of file + rm -f src/ncc/ThirdParty/Symfony/Yaml/autoload_spl.php diff --git a/src/autoload/autoload.php b/src/autoload/autoload.php index ef852e0..2bb52d7 100644 --- a/src/autoload/autoload.php +++ b/src/autoload/autoload.php @@ -21,7 +21,7 @@ $third_party_path . 'Symfony' . DIRECTORY_SEPARATOR . 'Process' . DIRECTORY_SEPARATOR . 'autoload_spl.php', $third_party_path . 'Symfony' . DIRECTORY_SEPARATOR . 'Uid' . DIRECTORY_SEPARATOR . 'autoload_spl.php', $third_party_path . 'Symfony' . DIRECTORY_SEPARATOR . 'Filesystem' . DIRECTORY_SEPARATOR . 'autoload_spl.php', - $third_party_path . 'austinhyde' . DIRECTORY_SEPARATOR . 'IniParser' . DIRECTORY_SEPARATOR . 'autoload_spl.php', + $third_party_path . 'Symfony' . DIRECTORY_SEPARATOR . 'Yaml' . DIRECTORY_SEPARATOR . 'autoload_spl.php', ]; foreach($target_files as $file) diff --git a/src/config/ncc.ini b/src/config/ncc.ini deleted file mode 100644 index f3b7d36..0000000 --- a/src/config/ncc.ini +++ /dev/null @@ -1,42 +0,0 @@ -[ncc] -; The default data directory that is used by NCC to store packages, -; cache, configuration files and other generated files. This includes -; data stored by packages using the NCC storage API. -data_directory=/var/ncc - - -[php] -; Configuration section for the PHP configuration that NCC will use to run - -; The main executable path for PHP that NCC should use -executable_path=/usr/bin/php - -; Enables/Disables the environment configuration feature -; Allowing packages to install environment configurations to NCC -; that can be loaded during runtime using the NCC API -; -; If disabled packages may break if they depend on this feature. -; -; Leaving this enabled while installing and using unknown packages -; without reviewing their source code could lead to potential security -; issues/backdoors, use this feature for containerized environments -enable_environment_configurations=False - -; Enables/Disables the injection of NCC's include path -; during the initialization phase allowing you to import -; packages using the `import()` function and other ncc -; API Functions without needing to require NCC's autoloader -; -; This feature is highly recommended to be enabled -enable_include_path=True - -[git] -executable_path=/usr/bin/git - -[composer] -; When enabled, NCC will use it's builtin version of composer -; to execute composer tasks, if disabled it will fallback to -; the `executable_path` option and attempt to use that specified -; location of composer -enable_internal_composer=True -executable_path=/home/user/composer.phar \ No newline at end of file diff --git a/src/config/ncc.yaml b/src/config/ncc.yaml new file mode 100644 index 0000000..ee7b398 --- /dev/null +++ b/src/config/ncc.yaml @@ -0,0 +1,41 @@ +ncc: + # The default data directory that is used by NCC to store packages, + # cache, configuration files and other generated files. This includes + # data stored by packages using the NCC storage API. + data_directory: "/var/ncc" + +php: + # Configuration section for the PHP configuration that NCC will use to run + + # The main executable path for PHP that NCC should use + executable_path: "/usr/bin/php" + + # Enables/Disables the environment configuration feature + # Allowing packages to install environment configurations to NCC + # that can be loaded during runtime using the NCC API + # + # If disabled packages may break if they depend on this feature. + # + # Leaving this enabled while installing and using unknown packages + # without reviewing their source code could lead to potential security + # issues/backdoors, use this feature for containerized environments + enable_environment_configurations: false + + # Enables/Disables the injection of NCC's include path + # during the initialization phase allowing you to import + # packages using the `import()` function and other ncc + # API Functions without needing to require NCC's autoloader + # + # This feature is highly recommended to be enabled + enable_include_path: true + +git: + executable_path: "/usr/bin/git" + +composer: + # When enabled, NCC will use it's builtin version of composer + # to execute composer tasks, if disabled it will fallback to + # the `executable_path` option and attempt to use that specified + # location of composer + enable_internal_composer: true + executable_path: "/home/user/composer.phar" \ No newline at end of file diff --git a/src/installer/generate_build_files.php b/src/installer/generate_build_files.php index c29c17b..6840c81 100644 --- a/src/installer/generate_build_files.php +++ b/src/installer/generate_build_files.php @@ -33,8 +33,9 @@ $excluded_files = [ 'hash_check.php', 'generate_build_files.php', + 'default_config.yaml', 'installer', - 'checksum.bin'. + 'checksum.bin', 'build_files', 'ncc.sh', 'extension' @@ -50,7 +51,7 @@ $build_files_content = []; foreach(scanContents(__DIR__) as $path) { - if(!in_array($path, $excluded_files)) + if(!in_array($path, $excluded_files, true)) { $build_files_content[] = $path; } diff --git a/src/installer/installer b/src/installer/installer index f9c5c40..9f84353 100644 --- a/src/installer/installer +++ b/src/installer/installer @@ -19,17 +19,19 @@ use ncc\Exceptions\InvalidScopeException; use ncc\Managers\CredentialManager; use ncc\ncc; -use ncc\Objects\CliHelpSection; -use ncc\ThirdParty\Symfony\Filesystem\Exception\IOException; + use ncc\Objects\CliHelpSection; + use ncc\ThirdParty\Symfony\Filesystem\Exception\IOException; use ncc\ThirdParty\Symfony\Filesystem\Filesystem; use ncc\ThirdParty\Symfony\Process\Exception\ProcessFailedException; - use ncc\ThirdParty\Symfony\process\PhpExecutableFinder; +use ncc\ThirdParty\Symfony\Process\ExecutableFinder; +use ncc\ThirdParty\Symfony\process\PhpExecutableFinder; use ncc\ThirdParty\Symfony\Process\Process; + use ncc\ThirdParty\Symfony\Yaml\Yaml; use ncc\Utilities\Console; -use ncc\Utilities\Functions; -use ncc\Utilities\PathFinder; -use ncc\Utilities\Resolver; -use ncc\Utilities\Validate; + use ncc\Utilities\Functions; + use ncc\Utilities\PathFinder; + use ncc\Utilities\Resolver; + use ncc\Utilities\Validate; use ncc\ZiProto\ZiProto; # Global Variables @@ -41,6 +43,34 @@ use ncc\Utilities\Validate; $NCC_PHP_EXECUTABLE=null; $NCC_FILESYSTEM=null; + /** + * A getParameter function to avoid code redundancy (Type-Safe) + * + * @param array|null $args + * @param string $option + * @param bool $require_content + * @return string|null + */ + function getParameter(?array $args, string $option, bool $require_content=true): ?string + { + if($args == null) + { + return null; + } + + if(!isset($args[$option])) + { + return null; + } + + if($require_content && ($args[$option] == null || strlen((string)$args[$option] == 0))) + { + return null; + } + + return $args[$option]; + } + // Require NCC if(!file_exists($NCC_AUTOLOAD)) { @@ -69,6 +99,8 @@ use ncc\Utilities\Validate; $NCC_ARGS = Resolver::parseArguments(implode(' ', $argv)); } + $NCC_AUTO_MODE = ($NCC_ARGS !== null && isset($NCC_ARGS['auto'])); + if(isset($NCC_ARGS['help'])) { $options = [ @@ -131,6 +163,7 @@ use ncc\Utilities\Validate; // Find the PHP executable $executable_finder = new PhpExecutableFinder(); $NCC_PHP_EXECUTABLE = $executable_finder->find(); + $NCC_EXECUTABLE_FINDER = new ExecutableFinder(); if(!$NCC_PHP_EXECUTABLE) { Console::outError('Cannot find PHP executable path'); @@ -142,6 +175,7 @@ use ncc\Utilities\Validate; __DIR__ . DIRECTORY_SEPARATOR . 'LICENSE', __DIR__ . DIRECTORY_SEPARATOR . 'build_files', __DIR__ . DIRECTORY_SEPARATOR . 'ncc.sh', + __DIR__ . DIRECTORY_SEPARATOR . 'default_config.yaml', ]; foreach($required_files as $path) { @@ -242,65 +276,124 @@ use ncc\Utilities\Validate; } // Determine the installation path - // TODO: Add the ability to change the data path as well - if($NCC_ARGS == null && !isset($NCC_ARGS['auto'])) + $skip_prompt = false; + $install_dir_arg = getParameter($NCC_ARGS, 'install-dir'); + + // Check the arguments + if($install_dir_arg !== null) + { + if(!Validate::unixFilepath($install_dir_arg)) + { + Console::outError('The given file path is not valid'); + exit(1); + } + + if(file_exists($install_dir_arg . DIRECTORY_SEPARATOR . 'ncc')) + { + Console::out('NCC Seems to already be installed, the installer will repair/upgrade your current install'); + $NCC_INSTALL_PATH = $install_dir_arg; + $skip_prompt = true; + } + else + { + Console::outError('The given directory already exists, it must be deleted before proceeding'); + exit(1); + } + } + + if(!$NCC_AUTO_MODE && !$skip_prompt) { while(true) { $user_input = null; $user_input = Console::getInput("Installation Path (Default: $NCC_INSTALL_PATH): "); - if(strlen($user_input) > 0) + if(strlen($user_input) > 0 && file_exists($user_input) && Validate::unixFilepath($user_input)) { - if(file_exists($user_input)) + if(file_exists($user_input . DIRECTORY_SEPARATOR . 'ncc')) { - if(file_exists($user_input . DIRECTORY_SEPARATOR . 'ncc')) - { - Console::out('NCC Seems to already be installed, the installer will repair/upgrade your current install'); - break; - } - else - { - Console::outError('The given directory already exists, it must be deleted before proceeding'); - } + $NCC_INSTALL_PATH = $user_input; + break; } else { - break; + Console::outError('The given directory already exists, it must be deleted before proceeding'); } } + elseif(strlen($user_input) > 0) + { + Console::outError('The given file path is not valid'); + } else { break; } } } - else + + + // Determine the data path + $skip_prompt = false; + $data_dir_arg = getParameter($NCC_ARGS, 'data-dir'); + + // Check the arguments + if($data_dir_arg !== null) { - if(strlen($NCC_ARGS['install-dir']) > 0) + if(!Validate::unixFilepath($data_dir_arg)) { - if(file_exists($NCC_ARGS['install-dir'])) + Console::outError('The given file path \''. $data_dir_arg . '\' is not valid'); + exit(1); + } + + if(file_exists($data_dir_arg . DIRECTORY_SEPARATOR . 'package.lck')) + { + $NCC_DATA_PATH = $data_dir_arg; + $skip_prompt = true; + } + else + { + Console::outError('The given directory \'' . $data_dir_arg . '\' already exists, it must be deleted before proceeding'); + exit(1); + } + } + + // Proceed with prompt if not in auto mode and argument was met + if(!$NCC_AUTO_MODE && !$skip_prompt) + { + while(true) + { + $user_input = null; + $user_input = Console::getInput("Data Path (Default: $NCC_DATA_PATH): "); + if(strlen($user_input) > 0 && file_exists($user_input) && Validate::unixFilepath($user_input)) { - if(file_exists($NCC_ARGS['install-dir'] . DIRECTORY_SEPARATOR . 'ncc')) + if(file_exists($user_input . DIRECTORY_SEPARATOR . 'package.lck')) { - Console::out('NCC Seems to already be installed, the installer will repair/upgrade your current install'); + $NCC_DATA_PATH = $user_input; + break; } else { Console::outError('The given directory already exists, it must be deleted before proceeding'); - exit(1); } } + elseif(strlen($user_input) > 0) + { + Console::outError('The given file path is not valid'); + } + else + { + break; + } } } // Ask to install composer if curl is available if($curl_available) { - if($NCC_ARGS !== null && isset($NCC_ARGS['auto']) && isset($NCC_ARGS['install-composer'])) + if(getParameter($NCC_ARGS, 'install-composer') !== null) { $update_composer = true; } - elseif(isset($NCC_ARGS['install-composer'])) + elseif(getParameter($NCC_ARGS, 'install-composer') !== null) { $update_composer = true; } @@ -310,8 +403,12 @@ use ncc\Utilities\Validate; $update_composer = Console::getBooleanInput('Do you want to install composer for NCC? (Recommended)'); } } + else + { + $update_composer = false; + } - if($NCC_ARGS == null && !isset($NCC_ARGS['auto'])) + if(!$NCC_AUTO_MODE) { if(!Console::getBooleanInput('Do you want install NCC?')) { @@ -320,6 +417,14 @@ use ncc\Utilities\Validate; } } + // Backup the configuration file + $config_backup = null; + if(file_exists($NCC_INSTALL_PATH . DIRECTORY_SEPARATOR . 'ncc.yaml')) + { + Console::out('ncc.yaml will be updated'); + $config_backup = file_get_contents($NCC_INSTALL_PATH . DIRECTORY_SEPARATOR . 'ncc.yaml'); + } + // Prepare installation if(file_exists($NCC_INSTALL_PATH)) { @@ -347,13 +452,9 @@ use ncc\Utilities\Validate; $NCC_DATA_PATH . DIRECTORY_SEPARATOR . 'ext', ]; - $NCC_FILESYSTEM->mkdir($required_dirs); - foreach($required_dirs as $dir) - { - $NCC_FILESYSTEM->chmod([$dir], 0755); - } - $NCC_FILESYSTEM->chmod([$NCC_DATA_PATH . DIRECTORY_SEPARATOR . 'config'], 0755); - $NCC_FILESYSTEM->chmod([$NCC_DATA_PATH . DIRECTORY_SEPARATOR . 'cache'], 0755); + $NCC_FILESYSTEM->mkdir($required_dirs, 0755); + $NCC_FILESYSTEM->chmod([$NCC_DATA_PATH . DIRECTORY_SEPARATOR . 'config'], 0777); + $NCC_FILESYSTEM->chmod([$NCC_DATA_PATH . DIRECTORY_SEPARATOR . 'cache'], 0777); // Install composer if($curl_available && $update_composer) @@ -390,12 +491,20 @@ use ncc\Utilities\Validate; } catch(ProcessFailedException $e) { - Console::outError('Cannot install composer, ' . $e->getMessage()); + Console::outError('Cannot install compos)er, ' . $e->getMessage()); + exit(1); + } + + // Verify install + if(!$NCC_FILESYSTEM->exists([$NCC_INSTALL_PATH . DIRECTORY_SEPARATOR . 'composer.phar'])) + { + Console::outError("The installation exited without any issues but composer doesn't seem to be installed correctly"); exit(1); } $NCC_FILESYSTEM->remove([$NCC_INSTALL_PATH . DIRECTORY_SEPARATOR . 'composer-setup.php']); $NCC_FILESYSTEM->chmod([$NCC_INSTALL_PATH . DIRECTORY_SEPARATOR . 'composer.phar'], 0755); + Console::out('Installed composer successfully'); } @@ -499,5 +608,63 @@ use ncc\Utilities\Validate; } } + // Create/Update configuration file + $config_obj = Yaml::parseFile(__DIR__ . DIRECTORY_SEPARATOR . 'default_config.yaml'); + + // Update the old configuration + if($config_backup !== null) + { + $old_config_obj = Yaml::parse($config_backup); + foreach($old_config_obj as $section => $value) + { + if(isset($config_obj[$section])) + { + foreach($value as $section_item) + { + if(!isset($config_obj[$section][$section_item])) + { + $config_obj[$section][$section_item] = $value; + } + } + } + else + { + $config_obj[$section] = $value; + } + } + } + + // Overwrite automatic values created by the installer + $config_obj['ncc']['data_directory'] = $NCC_DATA_PATH; + $config_obj['php']['executable_path'] = $NCC_PHP_EXECUTABLE; + $config_obj['git']['executable_path'] = $NCC_EXECUTABLE_FINDER->find('git'); + $config_obj['composer']['executable_path'] = $NCC_EXECUTABLE_FINDER->find('composer'); + + if($config_obj['git']['executable_path'] == null) + { + Console::outWarning('Cannot locate the executable path for \'git\', run \'ncc config --git.executable_path="GIT_PATH_HERE"\' as root to update the path'); + } + + if(!$update_composer) + { + Console::outWarning('Since composer is not installed alongside NCC, the installer will attempt to locate a install of composer on your system and configure NCC to use that'); + $config_obj['composer']['enable_internal_composer'] = false; + if($config_obj['composer']['executable_path'] == null) + { + Console::outWarning('Cannot locate the executable path for \'composer\', run \'ncc config --composer.executable_path="composer.phar"\' as root to update the path'); + } + } + + if($config_backup == null) + { + Console::out('Generating ncc.yaml'); + + } + else + { + Console::out('Updating ncc.yaml'); + } + + file_put_contents($NCC_INSTALL_PATH . DIRECTORY_SEPARATOR . 'ncc.yaml', Yaml::dump($config_obj)); Console::out('NCC has been successfully installed'); exit(0); \ No newline at end of file diff --git a/src/ncc/ThirdParty/Symfony/Yaml/CHANGELOG.md b/src/ncc/ThirdParty/Symfony/Yaml/CHANGELOG.md new file mode 100644 index 0000000..3fb33a8 --- /dev/null +++ b/src/ncc/ThirdParty/Symfony/Yaml/CHANGELOG.md @@ -0,0 +1,242 @@ +CHANGELOG +========= + +6.1 +--- + + * In cases where it will likely improve readability, strings containing single quotes will be double-quoted + +5.4 +--- + + * Add new `lint:yaml dirname --exclude=/dirname/foo.yaml --exclude=/dirname/bar.yaml` + option to exclude one or more specific files from multiple file list + * Allow negatable for the parse tags option with `--no-parse-tags` + +5.3 +--- + + * Added `github` format support & autodetection to render errors as annotations + when running the YAML linter command in a Github Action environment. + +5.1.0 +----- + + * Added support for parsing numbers prefixed with `0o` as octal numbers. + * Deprecated support for parsing numbers starting with `0` as octal numbers. They will be parsed as strings as of Symfony 6.0. Prefix numbers with `0o` + so that they are parsed as octal numbers. + + Before: + + ```yaml + Yaml::parse('072'); + ``` + + After: + + ```yaml + Yaml::parse('0o72'); + ``` + + * Added `yaml-lint` binary. + * Deprecated using the `!php/object` and `!php/const` tags without a value. + +5.0.0 +----- + + * Removed support for mappings inside multi-line strings. + * removed support for implicit STDIN usage in the `lint:yaml` command, use `lint:yaml -` (append a dash) instead to make it explicit. + +4.4.0 +----- + + * Added support for parsing the inline notation spanning multiple lines. + * Added support to dump `null` as `~` by using the `Yaml::DUMP_NULL_AS_TILDE` flag. + * deprecated accepting STDIN implicitly when using the `lint:yaml` command, use `lint:yaml -` (append a dash) instead to make it explicit. + +4.3.0 +----- + + * Using a mapping inside a multi-line string is deprecated and will throw a `ParseException` in 5.0. + +4.2.0 +----- + + * added support for multiple files or directories in `LintCommand` + +4.0.0 +----- + + * The behavior of the non-specific tag `!` is changed and now forces + non-evaluating your values. + * complex mappings will throw a `ParseException` + * support for the comma as a group separator for floats has been dropped, use + the underscore instead + * support for the `!!php/object` tag has been dropped, use the `!php/object` + tag instead + * duplicate mapping keys throw a `ParseException` + * non-string mapping keys throw a `ParseException`, use the `Yaml::PARSE_KEYS_AS_STRINGS` + flag to cast them to strings + * `%` at the beginning of an unquoted string throw a `ParseException` + * mappings with a colon (`:`) that is not followed by a whitespace throw a + `ParseException` + * the `Dumper::setIndentation()` method has been removed + * being able to pass boolean options to the `Yaml::parse()`, `Yaml::dump()`, + `Parser::parse()`, and `Dumper::dump()` methods to configure the behavior of + the parser and dumper is no longer supported, pass bitmask flags instead + * the constructor arguments of the `Parser` class have been removed + * the `Inline` class is internal and no longer part of the BC promise + * removed support for the `!str` tag, use the `!!str` tag instead + * added support for tagged scalars. + + ```yml + Yaml::parse('!foo bar', Yaml::PARSE_CUSTOM_TAGS); + // returns TaggedValue('foo', 'bar'); + ``` + +3.4.0 +----- + + * added support for parsing YAML files using the `Yaml::parseFile()` or `Parser::parseFile()` method + + * the `Dumper`, `Parser`, and `Yaml` classes are marked as final + + * Deprecated the `!php/object:` tag which will be replaced by the + `!php/object` tag (without the colon) in 4.0. + + * Deprecated the `!php/const:` tag which will be replaced by the + `!php/const` tag (without the colon) in 4.0. + + * Support for the `!str` tag is deprecated, use the `!!str` tag instead. + + * Deprecated using the non-specific tag `!` as its behavior will change in 4.0. + It will force non-evaluating your values in 4.0. Use plain integers or `!!float` instead. + +3.3.0 +----- + + * Starting an unquoted string with a question mark followed by a space is + deprecated and will throw a `ParseException` in Symfony 4.0. + + * Deprecated support for implicitly parsing non-string mapping keys as strings. + Mapping keys that are no strings will lead to a `ParseException` in Symfony + 4.0. Use quotes to opt-in for keys to be parsed as strings. + + Before: + + ```php + $yaml = << new A(), 'bar' => 1], 0, 0, Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE | Yaml::DUMP_OBJECT); + ``` + +3.0.0 +----- + + * Yaml::parse() now throws an exception when a blackslash is not escaped + in double-quoted strings + +2.8.0 +----- + + * Deprecated usage of a colon in an unquoted mapping value + * Deprecated usage of @, \`, | and > at the beginning of an unquoted string + * When surrounding strings with double-quotes, you must now escape `\` characters. Not + escaping those characters (when surrounded by double-quotes) is deprecated. + + Before: + + ```yml + class: "Foo\Var" + ``` + + After: + + ```yml + class: "Foo\\Var" + ``` + +2.1.0 +----- + + * Yaml::parse() does not evaluate loaded files as PHP files by default + anymore (call Yaml::enablePhpParsing() to get back the old behavior) diff --git a/src/ncc/ThirdParty/Symfony/Yaml/Command/LintCommand.php b/src/ncc/ThirdParty/Symfony/Yaml/Command/LintCommand.php new file mode 100644 index 0000000..d274c4e --- /dev/null +++ b/src/ncc/ThirdParty/Symfony/Yaml/Command/LintCommand.php @@ -0,0 +1,279 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace ncc\ThirdParty\Symfony\Yaml\Command; + +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\CI\GithubActionReporter; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Completion\CompletionInput; +use Symfony\Component\Console\Completion\CompletionSuggestions; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\RuntimeException; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use ncc\ThirdParty\Symfony\Yaml\Exception\ParseException; +use ncc\ThirdParty\Symfony\Yaml\Parser; +use ncc\ThirdParty\Symfony\Yaml\Yaml; + +/** + * Validates YAML files syntax and outputs encountered errors. + * + * @author Grégoire Pineau + * @author Robin Chalas + */ +#[AsCommand(name: 'lint:yaml', description: 'Lint a YAML file and outputs encountered errors')] +class LintCommand extends Command +{ + private Parser $parser; + private ?string $format = null; + private bool $displayCorrectFiles; + private ?\Closure $directoryIteratorProvider; + private ?\Closure $isReadableProvider; + + public function __construct(string $name = null, callable $directoryIteratorProvider = null, callable $isReadableProvider = null) + { + parent::__construct($name); + + $this->directoryIteratorProvider = null === $directoryIteratorProvider ? null : $directoryIteratorProvider(...); + $this->isReadableProvider = null === $isReadableProvider ? null : $isReadableProvider(...); + } + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this + ->addArgument('filename', InputArgument::IS_ARRAY, 'A file, a directory or "-" for reading from STDIN') + ->addOption('format', null, InputOption::VALUE_REQUIRED, 'The output format') + ->addOption('exclude', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Path(s) to exclude') + ->addOption('parse-tags', null, InputOption::VALUE_NEGATABLE, 'Parse custom tags', null) + ->setHelp(<<%command.name% command lints a YAML file and outputs to STDOUT +the first encountered syntax error. + +You can validates YAML contents passed from STDIN: + + cat filename | php %command.full_name% - + +You can also validate the syntax of a file: + + php %command.full_name% filename + +Or of a whole directory: + + php %command.full_name% dirname + php %command.full_name% dirname --format=json + +You can also exclude one or more specific files: + + php %command.full_name% dirname --exclude="dirname/foo.yaml" --exclude="dirname/bar.yaml" + +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + $filenames = (array) $input->getArgument('filename'); + $excludes = $input->getOption('exclude'); + $this->format = $input->getOption('format'); + $flags = $input->getOption('parse-tags'); + + if ('github' === $this->format && !class_exists(GithubActionReporter::class)) { + throw new \InvalidArgumentException('The "github" format is only available since "symfony/console" >= 5.3.'); + } + + if (null === $this->format) { + // Autodetect format according to CI environment + $this->format = class_exists(GithubActionReporter::class) && GithubActionReporter::isGithubActionEnvironment() ? 'github' : 'txt'; + } + + $flags = $flags ? Yaml::PARSE_CUSTOM_TAGS : 0; + + $this->displayCorrectFiles = $output->isVerbose(); + + if (['-'] === $filenames) { + return $this->display($io, [$this->validate(file_get_contents('php://stdin'), $flags)]); + } + + if (!$filenames) { + throw new RuntimeException('Please provide a filename or pipe file content to STDIN.'); + } + + $filesInfo = []; + foreach ($filenames as $filename) { + if (!$this->isReadable($filename)) { + throw new RuntimeException(sprintf('File or directory "%s" is not readable.', $filename)); + } + + foreach ($this->getFiles($filename) as $file) { + if (!\in_array($file->getPathname(), $excludes, true)) { + $filesInfo[] = $this->validate(file_get_contents($file), $flags, $file); + } + } + } + + return $this->display($io, $filesInfo); + } + + private function validate(string $content, int $flags, string $file = null) + { + $prevErrorHandler = set_error_handler(function ($level, $message, $file, $line) use (&$prevErrorHandler) { + if (\E_USER_DEPRECATED === $level) { + throw new ParseException($message, $this->getParser()->getRealCurrentLineNb() + 1); + } + + return $prevErrorHandler ? $prevErrorHandler($level, $message, $file, $line) : false; + }); + + try { + $this->getParser()->parse($content, Yaml::PARSE_CONSTANT | $flags); + } catch (ParseException $e) { + return ['file' => $file, 'line' => $e->getParsedLine(), 'valid' => false, 'message' => $e->getMessage()]; + } finally { + restore_error_handler(); + } + + return ['file' => $file, 'valid' => true]; + } + + private function display(SymfonyStyle $io, array $files): int + { + return match ($this->format) { + 'txt' => $this->displayTxt($io, $files), + 'json' => $this->displayJson($io, $files), + 'github' => $this->displayTxt($io, $files, true), + default => throw new InvalidArgumentException(sprintf('The format "%s" is not supported.', $this->format)), + }; + } + + private function displayTxt(SymfonyStyle $io, array $filesInfo, bool $errorAsGithubAnnotations = false): int + { + $countFiles = \count($filesInfo); + $erroredFiles = 0; + $suggestTagOption = false; + + if ($errorAsGithubAnnotations) { + $githubReporter = new GithubActionReporter($io); + } + + foreach ($filesInfo as $info) { + if ($info['valid'] && $this->displayCorrectFiles) { + $io->comment('OK'.($info['file'] ? sprintf(' in %s', $info['file']) : '')); + } elseif (!$info['valid']) { + ++$erroredFiles; + $io->text(' ERROR '.($info['file'] ? sprintf(' in %s', $info['file']) : '')); + $io->text(sprintf(' >> %s', $info['message'])); + + if (str_contains($info['message'], 'PARSE_CUSTOM_TAGS')) { + $suggestTagOption = true; + } + + if ($errorAsGithubAnnotations) { + $githubReporter->error($info['message'], $info['file'] ?? 'php://stdin', $info['line']); + } + } + } + + if (0 === $erroredFiles) { + $io->success(sprintf('All %d YAML files contain valid syntax.', $countFiles)); + } else { + $io->warning(sprintf('%d YAML files have valid syntax and %d contain errors.%s', $countFiles - $erroredFiles, $erroredFiles, $suggestTagOption ? ' Use the --parse-tags option if you want parse custom tags.' : '')); + } + + return min($erroredFiles, 1); + } + + private function displayJson(SymfonyStyle $io, array $filesInfo): int + { + $errors = 0; + + array_walk($filesInfo, function (&$v) use (&$errors) { + $v['file'] = (string) $v['file']; + if (!$v['valid']) { + ++$errors; + } + + if (isset($v['message']) && str_contains($v['message'], 'PARSE_CUSTOM_TAGS')) { + $v['message'] .= ' Use the --parse-tags option if you want parse custom tags.'; + } + }); + + $io->writeln(json_encode($filesInfo, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES)); + + return min($errors, 1); + } + + private function getFiles(string $fileOrDirectory): iterable + { + if (is_file($fileOrDirectory)) { + yield new \SplFileInfo($fileOrDirectory); + + return; + } + + foreach ($this->getDirectoryIterator($fileOrDirectory) as $file) { + if (!\in_array($file->getExtension(), ['yml', 'yaml'])) { + continue; + } + + yield $file; + } + } + + private function getParser(): Parser + { + return $this->parser ??= new Parser(); + } + + private function getDirectoryIterator(string $directory): iterable + { + $default = function ($directory) { + return new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($directory, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS), + \RecursiveIteratorIterator::LEAVES_ONLY + ); + }; + + if (null !== $this->directoryIteratorProvider) { + return ($this->directoryIteratorProvider)($directory, $default); + } + + return $default($directory); + } + + private function isReadable(string $fileOrDirectory): bool + { + $default = function ($fileOrDirectory) { + return is_readable($fileOrDirectory); + }; + + if (null !== $this->isReadableProvider) { + return ($this->isReadableProvider)($fileOrDirectory, $default); + } + + return $default($fileOrDirectory); + } + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if ($input->mustSuggestOptionValuesFor('format')) { + $suggestions->suggestValues(['txt', 'json', 'github']); + } + } +} diff --git a/src/ncc/ThirdParty/Symfony/Yaml/Dumper.php b/src/ncc/ThirdParty/Symfony/Yaml/Dumper.php new file mode 100644 index 0000000..0271406 --- /dev/null +++ b/src/ncc/ThirdParty/Symfony/Yaml/Dumper.php @@ -0,0 +1,138 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace ncc\ThirdParty\Symfony\Yaml; + +use ncc\ThirdParty\Symfony\Yaml\Tag\TaggedValue; + +/** + * Dumper dumps PHP variables to YAML strings. + * + * @author Fabien Potencier + * + * @final + */ +class Dumper +{ + /** + * The amount of spaces to use for indentation of nested nodes. + */ + private int $indentation; + + public function __construct(int $indentation = 4) + { + if ($indentation < 1) { + throw new \InvalidArgumentException('The indentation must be greater than zero.'); + } + + $this->indentation = $indentation; + } + + /** + * Dumps a PHP value to YAML. + * + * @param mixed $input The PHP value + * @param int $inline The level where you switch to inline YAML + * @param int $indent The level of indentation (used internally) + * @param int $flags A bit field of Yaml::DUMP_* constants to customize the dumped YAML string + */ + public function dump(mixed $input, int $inline = 0, int $indent = 0, int $flags = 0): string + { + $output = ''; + $prefix = $indent ? str_repeat(' ', $indent) : ''; + $dumpObjectAsInlineMap = true; + + if (Yaml::DUMP_OBJECT_AS_MAP & $flags && ($input instanceof \ArrayObject || $input instanceof \stdClass)) { + $dumpObjectAsInlineMap = empty((array) $input); + } + + if ($inline <= 0 || (!\is_array($input) && !$input instanceof TaggedValue && $dumpObjectAsInlineMap) || empty($input)) { + $output .= $prefix.Inline::dump($input, $flags); + } else { + $dumpAsMap = Inline::isHash($input); + + foreach ($input as $key => $value) { + if ('' !== $output && "\n" !== $output[-1]) { + $output .= "\n"; + } + + if (Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK & $flags && \is_string($value) && str_contains($value, "\n") && !str_contains($value, "\r")) { + // If the first line starts with a space character, the spec requires a blockIndicationIndicator + // http://www.yaml.org/spec/1.2/spec.html#id2793979 + $blockIndentationIndicator = str_starts_with($value, ' ') ? (string) $this->indentation : ''; + + if (isset($value[-2]) && "\n" === $value[-2] && "\n" === $value[-1]) { + $blockChompingIndicator = '+'; + } elseif ("\n" === $value[-1]) { + $blockChompingIndicator = ''; + } else { + $blockChompingIndicator = '-'; + } + + $output .= sprintf('%s%s%s |%s%s', $prefix, $dumpAsMap ? Inline::dump($key, $flags).':' : '-', '', $blockIndentationIndicator, $blockChompingIndicator); + + foreach (explode("\n", $value) as $row) { + if ('' === $row) { + $output .= "\n"; + } else { + $output .= sprintf("\n%s%s%s", $prefix, str_repeat(' ', $this->indentation), $row); + } + } + + continue; + } + + if ($value instanceof TaggedValue) { + $output .= sprintf('%s%s !%s', $prefix, $dumpAsMap ? Inline::dump($key, $flags).':' : '-', $value->getTag()); + + if (Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK & $flags && \is_string($value->getValue()) && str_contains($value->getValue(), "\n") && !str_contains($value->getValue(), "\r\n")) { + // If the first line starts with a space character, the spec requires a blockIndicationIndicator + // http://www.yaml.org/spec/1.2/spec.html#id2793979 + $blockIndentationIndicator = str_starts_with($value->getValue(), ' ') ? (string) $this->indentation : ''; + $output .= sprintf(' |%s', $blockIndentationIndicator); + + foreach (explode("\n", $value->getValue()) as $row) { + $output .= sprintf("\n%s%s%s", $prefix, str_repeat(' ', $this->indentation), $row); + } + + continue; + } + + if ($inline - 1 <= 0 || null === $value->getValue() || \is_scalar($value->getValue())) { + $output .= ' '.$this->dump($value->getValue(), $inline - 1, 0, $flags)."\n"; + } else { + $output .= "\n"; + $output .= $this->dump($value->getValue(), $inline - 1, $dumpAsMap ? $indent + $this->indentation : $indent + 2, $flags); + } + + continue; + } + + $dumpObjectAsInlineMap = true; + + if (Yaml::DUMP_OBJECT_AS_MAP & $flags && ($value instanceof \ArrayObject || $value instanceof \stdClass)) { + $dumpObjectAsInlineMap = empty((array) $value); + } + + $willBeInlined = $inline - 1 <= 0 || !\is_array($value) && $dumpObjectAsInlineMap || empty($value); + + $output .= sprintf('%s%s%s%s', + $prefix, + $dumpAsMap ? Inline::dump($key, $flags).':' : '-', + $willBeInlined ? ' ' : "\n", + $this->dump($value, $inline - 1, $willBeInlined ? 0 : $indent + $this->indentation, $flags) + ).($willBeInlined ? "\n" : ''); + } + } + + return $output; + } +} diff --git a/src/ncc/ThirdParty/Symfony/Yaml/Escaper.php b/src/ncc/ThirdParty/Symfony/Yaml/Escaper.php new file mode 100644 index 0000000..8cbfc1b --- /dev/null +++ b/src/ncc/ThirdParty/Symfony/Yaml/Escaper.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace ncc\ThirdParty\Symfony\Yaml; + +/** + * Escaper encapsulates escaping rules for single and double-quoted + * YAML strings. + * + * @author Matthew Lewinski + * + * @internal + */ +class Escaper +{ + // Characters that would cause a dumped string to require double quoting. + public const REGEX_CHARACTER_TO_ESCAPE = "[\\x00-\\x1f]|\x7f|\xc2\x85|\xc2\xa0|\xe2\x80\xa8|\xe2\x80\xa9"; + + // Mapping arrays for escaping a double quoted string. The backslash is + // first to ensure proper escaping because str_replace operates iteratively + // on the input arrays. This ordering of the characters avoids the use of strtr, + // which performs more slowly. + private const ESCAPEES = ['\\', '\\\\', '\\"', '"', + "\x00", "\x01", "\x02", "\x03", "\x04", "\x05", "\x06", "\x07", + "\x08", "\x09", "\x0a", "\x0b", "\x0c", "\x0d", "\x0e", "\x0f", + "\x10", "\x11", "\x12", "\x13", "\x14", "\x15", "\x16", "\x17", + "\x18", "\x19", "\x1a", "\x1b", "\x1c", "\x1d", "\x1e", "\x1f", + "\x7f", + "\xc2\x85", "\xc2\xa0", "\xe2\x80\xa8", "\xe2\x80\xa9", + ]; + private const ESCAPED = ['\\\\', '\\"', '\\\\', '\\"', + '\\0', '\\x01', '\\x02', '\\x03', '\\x04', '\\x05', '\\x06', '\\a', + '\\b', '\\t', '\\n', '\\v', '\\f', '\\r', '\\x0e', '\\x0f', + '\\x10', '\\x11', '\\x12', '\\x13', '\\x14', '\\x15', '\\x16', '\\x17', + '\\x18', '\\x19', '\\x1a', '\\e', '\\x1c', '\\x1d', '\\x1e', '\\x1f', + '\\x7f', + '\\N', '\\_', '\\L', '\\P', + ]; + + /** + * Determines if a PHP value would require double quoting in YAML. + * + * @param string $value A PHP value + */ + public static function requiresDoubleQuoting(string $value): bool + { + return 0 < preg_match('/'.self::REGEX_CHARACTER_TO_ESCAPE.'/u', $value); + } + + /** + * Escapes and surrounds a PHP value with double quotes. + * + * @param string $value A PHP value + */ + public static function escapeWithDoubleQuotes(string $value): string + { + return sprintf('"%s"', str_replace(self::ESCAPEES, self::ESCAPED, $value)); + } + + /** + * Determines if a PHP value would require single quoting in YAML. + * + * @param string $value A PHP value + */ + public static function requiresSingleQuoting(string $value): bool + { + // Determines if a PHP value is entirely composed of a value that would + // require single quoting in YAML. + if (\in_array(strtolower($value), ['null', '~', 'true', 'false', 'y', 'n', 'yes', 'no', 'on', 'off'])) { + return true; + } + + // Determines if the PHP value contains any single characters that would + // cause it to require single quoting in YAML. + return 0 < preg_match('/[ \s \' " \: \{ \} \[ \] , & \* \# \?] | \A[ \- ? | < > = ! % @ ` \p{Zs}]/xu', $value); + } + + /** + * Escapes and surrounds a PHP value with single quotes. + * + * @param string $value A PHP value + */ + public static function escapeWithSingleQuotes(string $value): string + { + return sprintf("'%s'", str_replace('\'', '\'\'', $value)); + } +} diff --git a/src/ncc/ThirdParty/Symfony/Yaml/Exception/DumpException.php b/src/ncc/ThirdParty/Symfony/Yaml/Exception/DumpException.php new file mode 100644 index 0000000..c84f8a8 --- /dev/null +++ b/src/ncc/ThirdParty/Symfony/Yaml/Exception/DumpException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace ncc\ThirdParty\Symfony\Yaml\Exception; + +/** + * Exception class thrown when an error occurs during dumping. + * + * @author Fabien Potencier + */ +class DumpException extends RuntimeException +{ +} diff --git a/src/ncc/ThirdParty/Symfony/Yaml/Exception/ExceptionInterface.php b/src/ncc/ThirdParty/Symfony/Yaml/Exception/ExceptionInterface.php new file mode 100644 index 0000000..17c829a --- /dev/null +++ b/src/ncc/ThirdParty/Symfony/Yaml/Exception/ExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace ncc\ThirdParty\Symfony\Yaml\Exception; + +/** + * Exception interface for all exceptions thrown by the component. + * + * @author Fabien Potencier + */ +interface ExceptionInterface extends \Throwable +{ +} diff --git a/src/ncc/ThirdParty/Symfony/Yaml/Exception/ParseException.php b/src/ncc/ThirdParty/Symfony/Yaml/Exception/ParseException.php new file mode 100644 index 0000000..88b10b7 --- /dev/null +++ b/src/ncc/ThirdParty/Symfony/Yaml/Exception/ParseException.php @@ -0,0 +1,126 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace ncc\ThirdParty\Symfony\Yaml\Exception; + +/** + * Exception class thrown when an error occurs during parsing. + * + * @author Fabien Potencier + */ +class ParseException extends RuntimeException +{ + private ?string $parsedFile; + private int $parsedLine; + private ?string $snippet; + private string $rawMessage; + + /** + * @param string $message The error message + * @param int $parsedLine The line where the error occurred + * @param string|null $snippet The snippet of code near the problem + * @param string|null $parsedFile The file name where the error occurred + */ + public function __construct(string $message, int $parsedLine = -1, string $snippet = null, string $parsedFile = null, \Throwable $previous = null) + { + $this->parsedFile = $parsedFile; + $this->parsedLine = $parsedLine; + $this->snippet = $snippet; + $this->rawMessage = $message; + + $this->updateRepr(); + + parent::__construct($this->message, 0, $previous); + } + + /** + * Gets the snippet of code near the error. + */ + public function getSnippet(): string + { + return $this->snippet; + } + + /** + * Sets the snippet of code near the error. + */ + public function setSnippet(string $snippet) + { + $this->snippet = $snippet; + + $this->updateRepr(); + } + + /** + * Gets the filename where the error occurred. + * + * This method returns null if a string is parsed. + */ + public function getParsedFile(): string + { + return $this->parsedFile; + } + + /** + * Sets the filename where the error occurred. + */ + public function setParsedFile(string $parsedFile) + { + $this->parsedFile = $parsedFile; + + $this->updateRepr(); + } + + /** + * Gets the line where the error occurred. + */ + public function getParsedLine(): int + { + return $this->parsedLine; + } + + /** + * Sets the line where the error occurred. + */ + public function setParsedLine(int $parsedLine) + { + $this->parsedLine = $parsedLine; + + $this->updateRepr(); + } + + private function updateRepr() + { + $this->message = $this->rawMessage; + + $dot = false; + if (str_ends_with($this->message, '.')) { + $this->message = substr($this->message, 0, -1); + $dot = true; + } + + if (null !== $this->parsedFile) { + $this->message .= sprintf(' in %s', json_encode($this->parsedFile, \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE)); + } + + if ($this->parsedLine >= 0) { + $this->message .= sprintf(' at line %d', $this->parsedLine); + } + + if ($this->snippet) { + $this->message .= sprintf(' (near "%s")', $this->snippet); + } + + if ($dot) { + $this->message .= '.'; + } + } +} diff --git a/src/ncc/ThirdParty/Symfony/Yaml/Exception/RuntimeException.php b/src/ncc/ThirdParty/Symfony/Yaml/Exception/RuntimeException.php new file mode 100644 index 0000000..300db89 --- /dev/null +++ b/src/ncc/ThirdParty/Symfony/Yaml/Exception/RuntimeException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace ncc\ThirdParty\Symfony\Yaml\Exception; + +/** + * Exception class thrown when an error occurs during parsing. + * + * @author Romain Neutron + */ +class RuntimeException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/src/ncc/ThirdParty/Symfony/Yaml/Inline.php b/src/ncc/ThirdParty/Symfony/Yaml/Inline.php new file mode 100644 index 0000000..ae06380 --- /dev/null +++ b/src/ncc/ThirdParty/Symfony/Yaml/Inline.php @@ -0,0 +1,779 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace ncc\ThirdParty\Symfony\Yaml; + +use ncc\ThirdParty\Symfony\Yaml\Exception\DumpException; +use ncc\ThirdParty\Symfony\Yaml\Exception\ParseException; +use ncc\ThirdParty\Symfony\Yaml\Tag\TaggedValue; + +/** + * Inline implements a YAML parser/dumper for the YAML inline syntax. + * + * @author Fabien Potencier + * + * @internal + */ +class Inline +{ + public const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*+(?:\\\\.[^"\\\\]*+)*+)"|\'([^\']*+(?:\'\'[^\']*+)*+)\')'; + + public static int $parsedLineNumber = -1; + public static ?string $parsedFilename = null; + + private static bool $exceptionOnInvalidType = false; + private static bool $objectSupport = false; + private static bool $objectForMap = false; + private static bool $constantSupport = false; + + public static function initialize(int $flags, int $parsedLineNumber = null, string $parsedFilename = null) + { + self::$exceptionOnInvalidType = (bool) (Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE & $flags); + self::$objectSupport = (bool) (Yaml::PARSE_OBJECT & $flags); + self::$objectForMap = (bool) (Yaml::PARSE_OBJECT_FOR_MAP & $flags); + self::$constantSupport = (bool) (Yaml::PARSE_CONSTANT & $flags); + self::$parsedFilename = $parsedFilename; + + if (null !== $parsedLineNumber) { + self::$parsedLineNumber = $parsedLineNumber; + } + } + + /** + * Converts a YAML string to a PHP value. + * + * @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior + * @param array $references Mapping of variable names to values + * + * @throws ParseException + */ + public static function parse(string $value = null, int $flags = 0, array &$references = []): mixed + { + self::initialize($flags); + + $value = trim($value); + + if ('' === $value) { + return ''; + } + + $i = 0; + $tag = self::parseTag($value, $i, $flags); + switch ($value[$i]) { + case '[': + $result = self::parseSequence($value, $flags, $i, $references); + ++$i; + break; + case '{': + $result = self::parseMapping($value, $flags, $i, $references); + ++$i; + break; + default: + $result = self::parseScalar($value, $flags, null, $i, null === $tag, $references); + } + + // some comments are allowed at the end + if (preg_replace('/\s*#.*$/A', '', substr($value, $i))) { + throw new ParseException(sprintf('Unexpected characters near "%s".', substr($value, $i)), self::$parsedLineNumber + 1, $value, self::$parsedFilename); + } + + if (null !== $tag && '' !== $tag) { + return new TaggedValue($tag, $result); + } + + return $result; + } + + /** + * Dumps a given PHP variable to a YAML string. + * + * @param mixed $value The PHP variable to convert + * @param int $flags A bit field of Yaml::DUMP_* constants to customize the dumped YAML string + * + * @throws DumpException When trying to dump PHP resource + */ + public static function dump(mixed $value, int $flags = 0): string + { + switch (true) { + case \is_resource($value): + if (Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE & $flags) { + throw new DumpException(sprintf('Unable to dump PHP resources in a YAML file ("%s").', get_resource_type($value))); + } + + return self::dumpNull($flags); + case $value instanceof \DateTimeInterface: + return $value->format('c'); + case $value instanceof \UnitEnum: + return sprintf('!php/const %s::%s', \get_class($value), $value->name); + case \is_object($value): + if ($value instanceof TaggedValue) { + return '!'.$value->getTag().' '.self::dump($value->getValue(), $flags); + } + + if (Yaml::DUMP_OBJECT & $flags) { + return '!php/object '.self::dump(serialize($value)); + } + + if (Yaml::DUMP_OBJECT_AS_MAP & $flags && ($value instanceof \stdClass || $value instanceof \ArrayObject)) { + $output = []; + + foreach ($value as $key => $val) { + $output[] = sprintf('%s: %s', self::dump($key, $flags), self::dump($val, $flags)); + } + + return sprintf('{ %s }', implode(', ', $output)); + } + + if (Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE & $flags) { + throw new DumpException('Object support when dumping a YAML file has been disabled.'); + } + + return self::dumpNull($flags); + case \is_array($value): + return self::dumpArray($value, $flags); + case null === $value: + return self::dumpNull($flags); + case true === $value: + return 'true'; + case false === $value: + return 'false'; + case \is_int($value): + return $value; + case is_numeric($value) && false === strpbrk($value, "\f\n\r\t\v"): + $locale = setlocale(\LC_NUMERIC, 0); + if (false !== $locale) { + setlocale(\LC_NUMERIC, 'C'); + } + if (\is_float($value)) { + $repr = (string) $value; + if (is_infinite($value)) { + $repr = str_ireplace('INF', '.Inf', $repr); + } elseif (floor($value) == $value && $repr == $value) { + // Preserve float data type since storing a whole number will result in integer value. + if (!str_contains($repr, 'E')) { + $repr = $repr.'.0'; + } + } + } else { + $repr = \is_string($value) ? "'$value'" : (string) $value; + } + if (false !== $locale) { + setlocale(\LC_NUMERIC, $locale); + } + + return $repr; + case '' == $value: + return "''"; + case self::isBinaryString($value): + return '!!binary '.base64_encode($value); + case Escaper::requiresDoubleQuoting($value): + return Escaper::escapeWithDoubleQuotes($value); + case Escaper::requiresSingleQuoting($value): + $singleQuoted = Escaper::escapeWithSingleQuotes($value); + if (!str_contains($value, "'")) { + return $singleQuoted; + } + // Attempt double-quoting the string instead to see if it's more efficient. + $doubleQuoted = Escaper::escapeWithDoubleQuotes($value); + + return \strlen($doubleQuoted) < \strlen($singleQuoted) ? $doubleQuoted : $singleQuoted; + case Parser::preg_match('{^[0-9]+[_0-9]*$}', $value): + case Parser::preg_match(self::getHexRegex(), $value): + case Parser::preg_match(self::getTimestampRegex(), $value): + return Escaper::escapeWithSingleQuotes($value); + default: + return $value; + } + } + + /** + * Check if given array is hash or just normal indexed array. + */ + public static function isHash(array|\ArrayObject|\stdClass $value): bool + { + if ($value instanceof \stdClass || $value instanceof \ArrayObject) { + return true; + } + + $expectedKey = 0; + + foreach ($value as $key => $val) { + if ($key !== $expectedKey++) { + return true; + } + } + + return false; + } + + /** + * Dumps a PHP array to a YAML string. + * + * @param array $value The PHP array to dump + * @param int $flags A bit field of Yaml::DUMP_* constants to customize the dumped YAML string + */ + private static function dumpArray(array $value, int $flags): string + { + // array + if (($value || Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE & $flags) && !self::isHash($value)) { + $output = []; + foreach ($value as $val) { + $output[] = self::dump($val, $flags); + } + + return sprintf('[%s]', implode(', ', $output)); + } + + // hash + $output = []; + foreach ($value as $key => $val) { + $output[] = sprintf('%s: %s', self::dump($key, $flags), self::dump($val, $flags)); + } + + return sprintf('{ %s }', implode(', ', $output)); + } + + private static function dumpNull(int $flags): string + { + if (Yaml::DUMP_NULL_AS_TILDE & $flags) { + return '~'; + } + + return 'null'; + } + + /** + * Parses a YAML scalar. + * + * @throws ParseException When malformed inline YAML string is parsed + */ + public static function parseScalar(string $scalar, int $flags = 0, array $delimiters = null, int &$i = 0, bool $evaluate = true, array &$references = [], bool &$isQuoted = null): mixed + { + if (\in_array($scalar[$i], ['"', "'"], true)) { + // quoted scalar + $isQuoted = true; + $output = self::parseQuotedScalar($scalar, $i); + + if (null !== $delimiters) { + $tmp = ltrim(substr($scalar, $i), " \n"); + if ('' === $tmp) { + throw new ParseException(sprintf('Unexpected end of line, expected one of "%s".', implode('', $delimiters)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + if (!\in_array($tmp[0], $delimiters)) { + throw new ParseException(sprintf('Unexpected characters (%s).', substr($scalar, $i)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + } + } else { + // "normal" string + $isQuoted = false; + + if (!$delimiters) { + $output = substr($scalar, $i); + $i += \strlen($output); + + // remove comments + if (Parser::preg_match('/[ \t]+#/', $output, $match, \PREG_OFFSET_CAPTURE)) { + $output = substr($output, 0, $match[0][1]); + } + } elseif (Parser::preg_match('/^(.*?)('.implode('|', $delimiters).')/', substr($scalar, $i), $match)) { + $output = $match[1]; + $i += \strlen($output); + $output = trim($output); + } else { + throw new ParseException(sprintf('Malformed inline YAML string: "%s".', $scalar), self::$parsedLineNumber + 1, null, self::$parsedFilename); + } + + // a non-quoted string cannot start with @ or ` (reserved) nor with a scalar indicator (| or >) + if ($output && ('@' === $output[0] || '`' === $output[0] || '|' === $output[0] || '>' === $output[0] || '%' === $output[0])) { + throw new ParseException(sprintf('The reserved indicator "%s" cannot start a plain scalar; you need to quote the scalar.', $output[0]), self::$parsedLineNumber + 1, $output, self::$parsedFilename); + } + + if ($evaluate) { + $output = self::evaluateScalar($output, $flags, $references, $isQuoted); + } + } + + return $output; + } + + /** + * Parses a YAML quoted scalar. + * + * @throws ParseException When malformed inline YAML string is parsed + */ + private static function parseQuotedScalar(string $scalar, int &$i = 0): string + { + if (!Parser::preg_match('/'.self::REGEX_QUOTED_STRING.'/Au', substr($scalar, $i), $match)) { + throw new ParseException(sprintf('Malformed inline YAML string: "%s".', substr($scalar, $i)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + + $output = substr($match[0], 1, -1); + + $unescaper = new Unescaper(); + if ('"' == $scalar[$i]) { + $output = $unescaper->unescapeDoubleQuotedString($output); + } else { + $output = $unescaper->unescapeSingleQuotedString($output); + } + + $i += \strlen($match[0]); + + return $output; + } + + /** + * Parses a YAML sequence. + * + * @throws ParseException When malformed inline YAML string is parsed + */ + private static function parseSequence(string $sequence, int $flags, int &$i = 0, array &$references = []): array + { + $output = []; + $len = \strlen($sequence); + ++$i; + + // [foo, bar, ...] + while ($i < $len) { + if (']' === $sequence[$i]) { + return $output; + } + if (',' === $sequence[$i] || ' ' === $sequence[$i]) { + ++$i; + + continue; + } + + $tag = self::parseTag($sequence, $i, $flags); + switch ($sequence[$i]) { + case '[': + // nested sequence + $value = self::parseSequence($sequence, $flags, $i, $references); + break; + case '{': + // nested mapping + $value = self::parseMapping($sequence, $flags, $i, $references); + break; + default: + $value = self::parseScalar($sequence, $flags, [',', ']'], $i, null === $tag, $references, $isQuoted); + + // the value can be an array if a reference has been resolved to an array var + if (\is_string($value) && !$isQuoted && str_contains($value, ': ')) { + // embedded mapping? + try { + $pos = 0; + $value = self::parseMapping('{'.$value.'}', $flags, $pos, $references); + } catch (\InvalidArgumentException) { + // no, it's not + } + } + + if (!$isQuoted && \is_string($value) && '' !== $value && '&' === $value[0] && Parser::preg_match(Parser::REFERENCE_PATTERN, $value, $matches)) { + $references[$matches['ref']] = $matches['value']; + $value = $matches['value']; + } + + --$i; + } + + if (null !== $tag && '' !== $tag) { + $value = new TaggedValue($tag, $value); + } + + $output[] = $value; + + ++$i; + } + + throw new ParseException(sprintf('Malformed inline YAML string: "%s".', $sequence), self::$parsedLineNumber + 1, null, self::$parsedFilename); + } + + /** + * Parses a YAML mapping. + * + * @throws ParseException When malformed inline YAML string is parsed + */ + private static function parseMapping(string $mapping, int $flags, int &$i = 0, array &$references = []): array|\stdClass + { + $output = []; + $len = \strlen($mapping); + ++$i; + $allowOverwrite = false; + + // {foo: bar, bar:foo, ...} + while ($i < $len) { + switch ($mapping[$i]) { + case ' ': + case ',': + case "\n": + ++$i; + continue 2; + case '}': + if (self::$objectForMap) { + return (object) $output; + } + + return $output; + } + + // key + $offsetBeforeKeyParsing = $i; + $isKeyQuoted = \in_array($mapping[$i], ['"', "'"], true); + $key = self::parseScalar($mapping, $flags, [':', ' '], $i, false); + + if ($offsetBeforeKeyParsing === $i) { + throw new ParseException('Missing mapping key.', self::$parsedLineNumber + 1, $mapping); + } + + if ('!php/const' === $key) { + $key .= ' '.self::parseScalar($mapping, $flags, [':'], $i, false); + $key = self::evaluateScalar($key, $flags); + } + + if (false === $i = strpos($mapping, ':', $i)) { + break; + } + + if (!$isKeyQuoted) { + $evaluatedKey = self::evaluateScalar($key, $flags, $references); + + if ('' !== $key && $evaluatedKey !== $key && !\is_string($evaluatedKey) && !\is_int($evaluatedKey)) { + throw new ParseException('Implicit casting of incompatible mapping keys to strings is not supported. Quote your evaluable mapping keys instead.', self::$parsedLineNumber + 1, $mapping); + } + } + + if (!$isKeyQuoted && (!isset($mapping[$i + 1]) || !\in_array($mapping[$i + 1], [' ', ',', '[', ']', '{', '}', "\n"], true))) { + throw new ParseException('Colons must be followed by a space or an indication character (i.e. " ", ",", "[", "]", "{", "}").', self::$parsedLineNumber + 1, $mapping); + } + + if ('<<' === $key) { + $allowOverwrite = true; + } + + while ($i < $len) { + if (':' === $mapping[$i] || ' ' === $mapping[$i] || "\n" === $mapping[$i]) { + ++$i; + + continue; + } + + $tag = self::parseTag($mapping, $i, $flags); + switch ($mapping[$i]) { + case '[': + // nested sequence + $value = self::parseSequence($mapping, $flags, $i, $references); + // Spec: Keys MUST be unique; first one wins. + // Parser cannot abort this mapping earlier, since lines + // are processed sequentially. + // But overwriting is allowed when a merge node is used in current block. + if ('<<' === $key) { + foreach ($value as $parsedValue) { + $output += $parsedValue; + } + } elseif ($allowOverwrite || !isset($output[$key])) { + if (null !== $tag) { + $output[$key] = new TaggedValue($tag, $value); + } else { + $output[$key] = $value; + } + } elseif (isset($output[$key])) { + throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), self::$parsedLineNumber + 1, $mapping); + } + break; + case '{': + // nested mapping + $value = self::parseMapping($mapping, $flags, $i, $references); + // Spec: Keys MUST be unique; first one wins. + // Parser cannot abort this mapping earlier, since lines + // are processed sequentially. + // But overwriting is allowed when a merge node is used in current block. + if ('<<' === $key) { + $output += $value; + } elseif ($allowOverwrite || !isset($output[$key])) { + if (null !== $tag) { + $output[$key] = new TaggedValue($tag, $value); + } else { + $output[$key] = $value; + } + } elseif (isset($output[$key])) { + throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), self::$parsedLineNumber + 1, $mapping); + } + break; + default: + $value = self::parseScalar($mapping, $flags, [',', '}', "\n"], $i, null === $tag, $references, $isValueQuoted); + // Spec: Keys MUST be unique; first one wins. + // Parser cannot abort this mapping earlier, since lines + // are processed sequentially. + // But overwriting is allowed when a merge node is used in current block. + if ('<<' === $key) { + $output += $value; + } elseif ($allowOverwrite || !isset($output[$key])) { + if (!$isValueQuoted && \is_string($value) && '' !== $value && '&' === $value[0] && Parser::preg_match(Parser::REFERENCE_PATTERN, $value, $matches)) { + $references[$matches['ref']] = $matches['value']; + $value = $matches['value']; + } + + if (null !== $tag) { + $output[$key] = new TaggedValue($tag, $value); + } else { + $output[$key] = $value; + } + } elseif (isset($output[$key])) { + throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), self::$parsedLineNumber + 1, $mapping); + } + --$i; + } + ++$i; + + continue 2; + } + } + + throw new ParseException(sprintf('Malformed inline YAML string: "%s".', $mapping), self::$parsedLineNumber + 1, null, self::$parsedFilename); + } + + /** + * Evaluates scalars and replaces magic values. + * + * @throws ParseException when object parsing support was disabled and the parser detected a PHP object or when a reference could not be resolved + */ + private static function evaluateScalar(string $scalar, int $flags, array &$references = [], bool &$isQuotedString = null): mixed + { + $isQuotedString = false; + $scalar = trim($scalar); + + if (str_starts_with($scalar, '*')) { + if (false !== $pos = strpos($scalar, '#')) { + $value = substr($scalar, 1, $pos - 2); + } else { + $value = substr($scalar, 1); + } + + // an unquoted * + if (false === $value || '' === $value) { + throw new ParseException('A reference must contain at least one character.', self::$parsedLineNumber + 1, $value, self::$parsedFilename); + } + + if (!\array_key_exists($value, $references)) { + throw new ParseException(sprintf('Reference "%s" does not exist.', $value), self::$parsedLineNumber + 1, $value, self::$parsedFilename); + } + + return $references[$value]; + } + + $scalarLower = strtolower($scalar); + + switch (true) { + case 'null' === $scalarLower: + case '' === $scalar: + case '~' === $scalar: + return null; + case 'true' === $scalarLower: + return true; + case 'false' === $scalarLower: + return false; + case '!' === $scalar[0]: + switch (true) { + case str_starts_with($scalar, '!!str '): + $s = (string) substr($scalar, 6); + + if (\in_array($s[0] ?? '', ['"', "'"], true)) { + $isQuotedString = true; + $s = self::parseQuotedScalar($s); + } + + return $s; + case str_starts_with($scalar, '! '): + return substr($scalar, 2); + case str_starts_with($scalar, '!php/object'): + if (self::$objectSupport) { + if (!isset($scalar[12])) { + throw new ParseException('Missing value for tag "!php/object".', self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + + return unserialize(self::parseScalar(substr($scalar, 12))); + } + + if (self::$exceptionOnInvalidType) { + throw new ParseException('Object support when parsing a YAML file has been disabled.', self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + + return null; + case str_starts_with($scalar, '!php/const'): + if (self::$constantSupport) { + if (!isset($scalar[11])) { + throw new ParseException('Missing value for tag "!php/const".', self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + + $i = 0; + if (\defined($const = self::parseScalar(substr($scalar, 11), 0, null, $i, false))) { + return \constant($const); + } + + throw new ParseException(sprintf('The constant "%s" is not defined.', $const), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + if (self::$exceptionOnInvalidType) { + throw new ParseException(sprintf('The string "%s" could not be parsed as a constant. Did you forget to pass the "Yaml::PARSE_CONSTANT" flag to the parser?', $scalar), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + + return null; + case str_starts_with($scalar, '!!float '): + return (float) substr($scalar, 8); + case str_starts_with($scalar, '!!binary '): + return self::evaluateBinaryScalar(substr($scalar, 9)); + } + + throw new ParseException(sprintf('The string "%s" could not be parsed as it uses an unsupported built-in tag.', $scalar), self::$parsedLineNumber, $scalar, self::$parsedFilename); + case preg_match('/^(?:\+|-)?0o(?P[0-7_]++)$/', $scalar, $matches): + $value = str_replace('_', '', $matches['value']); + + if ('-' === $scalar[0]) { + return -octdec($value); + } + + return octdec($value); + // Optimize for returning strings. + case \in_array($scalar[0], ['+', '-', '.'], true) || is_numeric($scalar[0]): + if (Parser::preg_match('{^[+-]?[0-9][0-9_]*$}', $scalar)) { + $scalar = str_replace('_', '', $scalar); + } + + switch (true) { + case ctype_digit($scalar): + case '-' === $scalar[0] && ctype_digit(substr($scalar, 1)): + $cast = (int) $scalar; + + return ($scalar === (string) $cast) ? $cast : $scalar; + case is_numeric($scalar): + case Parser::preg_match(self::getHexRegex(), $scalar): + $scalar = str_replace('_', '', $scalar); + + return '0x' === $scalar[0].$scalar[1] ? hexdec($scalar) : (float) $scalar; + case '.inf' === $scalarLower: + case '.nan' === $scalarLower: + return -log(0); + case '-.inf' === $scalarLower: + return log(0); + case Parser::preg_match('/^(-|\+)?[0-9][0-9_]*(\.[0-9_]+)?$/', $scalar): + return (float) str_replace('_', '', $scalar); + case Parser::preg_match(self::getTimestampRegex(), $scalar): + // When no timezone is provided in the parsed date, YAML spec says we must assume UTC. + $time = new \DateTime($scalar, new \DateTimeZone('UTC')); + + if (Yaml::PARSE_DATETIME & $flags) { + return $time; + } + + try { + if (false !== $scalar = $time->getTimestamp()) { + return $scalar; + } + } catch (\ValueError) { + // no-op + } + + return $time->format('U'); + } + } + + return (string) $scalar; + } + + private static function parseTag(string $value, int &$i, int $flags): ?string + { + if ('!' !== $value[$i]) { + return null; + } + + $tagLength = strcspn($value, " \t\n[]{},", $i + 1); + $tag = substr($value, $i + 1, $tagLength); + + $nextOffset = $i + $tagLength + 1; + $nextOffset += strspn($value, ' ', $nextOffset); + + if ('' === $tag && (!isset($value[$nextOffset]) || \in_array($value[$nextOffset], [']', '}', ','], true))) { + throw new ParseException('Using the unquoted scalar value "!" is not supported. You must quote it.', self::$parsedLineNumber + 1, $value, self::$parsedFilename); + } + + // Is followed by a scalar and is a built-in tag + if ('' !== $tag && (!isset($value[$nextOffset]) || !\in_array($value[$nextOffset], ['[', '{'], true)) && ('!' === $tag[0] || 'str' === $tag || 'php/const' === $tag || 'php/object' === $tag)) { + // Manage in {@link self::evaluateScalar()} + return null; + } + + $i = $nextOffset; + + // Built-in tags + if ('' !== $tag && '!' === $tag[0]) { + throw new ParseException(sprintf('The built-in tag "!%s" is not implemented.', $tag), self::$parsedLineNumber + 1, $value, self::$parsedFilename); + } + + if ('' !== $tag && !isset($value[$i])) { + throw new ParseException(sprintf('Missing value for tag "%s".', $tag), self::$parsedLineNumber + 1, $value, self::$parsedFilename); + } + + if ('' === $tag || Yaml::PARSE_CUSTOM_TAGS & $flags) { + return $tag; + } + + throw new ParseException(sprintf('Tags support is not enabled. Enable the "Yaml::PARSE_CUSTOM_TAGS" flag to use "!%s".', $tag), self::$parsedLineNumber + 1, $value, self::$parsedFilename); + } + + public static function evaluateBinaryScalar(string $scalar): string + { + $parsedBinaryData = self::parseScalar(preg_replace('/\s/', '', $scalar)); + + if (0 !== (\strlen($parsedBinaryData) % 4)) { + throw new ParseException(sprintf('The normalized base64 encoded data (data without whitespace characters) length must be a multiple of four (%d bytes given).', \strlen($parsedBinaryData)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + + if (!Parser::preg_match('#^[A-Z0-9+/]+={0,2}$#i', $parsedBinaryData)) { + throw new ParseException(sprintf('The base64 encoded data (%s) contains invalid characters.', $parsedBinaryData), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename); + } + + return base64_decode($parsedBinaryData, true); + } + + private static function isBinaryString(string $value): bool + { + return !preg_match('//u', $value) || preg_match('/[^\x00\x07-\x0d\x1B\x20-\xff]/', $value); + } + + /** + * Gets a regex that matches a YAML date. + * + * @see http://www.yaml.org/spec/1.2/spec.html#id2761573 + */ + private static function getTimestampRegex(): string + { + return <<[0-9][0-9][0-9][0-9]) + -(?P[0-9][0-9]?) + -(?P[0-9][0-9]?) + (?:(?:[Tt]|[ \t]+) + (?P[0-9][0-9]?) + :(?P[0-9][0-9]) + :(?P[0-9][0-9]) + (?:\.(?P[0-9]*))? + (?:[ \t]*(?PZ|(?P[-+])(?P[0-9][0-9]?) + (?::(?P[0-9][0-9]))?))?)? + $~x +EOF; + } + + /** + * Gets a regex that matches a YAML number in hexadecimal notation. + */ + private static function getHexRegex(): string + { + return '~^0x[0-9a-f_]++$~i'; + } +} diff --git a/src/ncc/ThirdParty/austinhyde/IniParser/LICENSE b/src/ncc/ThirdParty/Symfony/Yaml/LICENSE similarity index 82% rename from src/ncc/ThirdParty/austinhyde/IniParser/LICENSE rename to src/ncc/ThirdParty/Symfony/Yaml/LICENSE index 463eabf..88bf75b 100644 --- a/src/ncc/ThirdParty/austinhyde/IniParser/LICENSE +++ b/src/ncc/ThirdParty/Symfony/Yaml/LICENSE @@ -1,16 +1,14 @@ -The MIT License (MIT) - -Copyright (c) 2013 Austin Hyde +Copyright (c) 2004-2022 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, @@ -18,4 +16,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. \ No newline at end of file +THE SOFTWARE. diff --git a/src/ncc/ThirdParty/Symfony/Yaml/Parser.php b/src/ncc/ThirdParty/Symfony/Yaml/Parser.php new file mode 100644 index 0000000..eca542a --- /dev/null +++ b/src/ncc/ThirdParty/Symfony/Yaml/Parser.php @@ -0,0 +1,1255 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace ncc\ThirdParty\Symfony\Yaml; + +use ncc\ThirdParty\Symfony\Yaml\Exception\ParseException; +use ncc\ThirdParty\Symfony\Yaml\Tag\TaggedValue; + +/** + * Parser parses YAML strings to convert them to PHP arrays. + * + * @author Fabien Potencier + * + * @final + */ +class Parser +{ + public const TAG_PATTERN = '(?P![\w!.\/:-]+)'; + public const BLOCK_SCALAR_HEADER_PATTERN = '(?P\||>)(?P\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?(?P +#.*)?'; + public const REFERENCE_PATTERN = '#^&(?P[^ ]++) *+(?P.*)#u'; + + private ?string $filename = null; + private int $offset = 0; + private int $numberOfParsedLines = 0; + private ?int $totalNumberOfLines = null; + private array $lines = []; + private int $currentLineNb = -1; + private string $currentLine = ''; + private array $refs = []; + private array $skippedLineNumbers = []; + private array $locallySkippedLineNumbers = []; + private array $refsBeingParsed = []; + + /** + * Parses a YAML file into a PHP value. + * + * @param string $filename The path to the YAML file to be parsed + * @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior + * + * @throws ParseException If the file could not be read or the YAML is not valid + */ + public function parseFile(string $filename, int $flags = 0): mixed + { + if (!is_file($filename)) { + throw new ParseException(sprintf('File "%s" does not exist.', $filename)); + } + + if (!is_readable($filename)) { + throw new ParseException(sprintf('File "%s" cannot be read.', $filename)); + } + + $this->filename = $filename; + + try { + return $this->parse(file_get_contents($filename), $flags); + } finally { + $this->filename = null; + } + } + + /** + * Parses a YAML string to a PHP value. + * + * @param string $value A YAML string + * @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior + * + * @throws ParseException If the YAML is not valid + */ + public function parse(string $value, int $flags = 0): mixed + { + if (false === preg_match('//u', $value)) { + throw new ParseException('The YAML value does not appear to be valid UTF-8.', -1, null, $this->filename); + } + + $this->refs = []; + + try { + $data = $this->doParse($value, $flags); + } finally { + $this->refsBeingParsed = []; + $this->offset = 0; + $this->lines = []; + $this->currentLine = ''; + $this->numberOfParsedLines = 0; + $this->refs = []; + $this->skippedLineNumbers = []; + $this->locallySkippedLineNumbers = []; + $this->totalNumberOfLines = null; + } + + return $data; + } + + private function doParse(string $value, int $flags) + { + $this->currentLineNb = -1; + $this->currentLine = ''; + $value = $this->cleanup($value); + $this->lines = explode("\n", $value); + $this->numberOfParsedLines = \count($this->lines); + $this->locallySkippedLineNumbers = []; + + if (null === $this->totalNumberOfLines) { + $this->totalNumberOfLines = $this->numberOfParsedLines; + } + + if (!$this->moveToNextLine()) { + return null; + } + + $data = []; + $context = null; + $allowOverwrite = false; + + while ($this->isCurrentLineEmpty()) { + if (!$this->moveToNextLine()) { + return null; + } + } + + // Resolves the tag and returns if end of the document + if (null !== ($tag = $this->getLineTag($this->currentLine, $flags, false)) && !$this->moveToNextLine()) { + return new TaggedValue($tag, ''); + } + + do { + if ($this->isCurrentLineEmpty()) { + continue; + } + + // tab? + if ("\t" === $this->currentLine[0]) { + throw new ParseException('A YAML file cannot contain tabs as indentation.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + + Inline::initialize($flags, $this->getRealCurrentLineNb(), $this->filename); + + $isRef = $mergeNode = false; + if ('-' === $this->currentLine[0] && self::preg_match('#^\-((?P\s+)(?P.+))?$#u', rtrim($this->currentLine), $values)) { + if ($context && 'mapping' == $context) { + throw new ParseException('You cannot define a sequence item when in a mapping.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + $context = 'sequence'; + + if (isset($values['value']) && '&' === $values['value'][0] && self::preg_match(self::REFERENCE_PATTERN, $values['value'], $matches)) { + $isRef = $matches['ref']; + $this->refsBeingParsed[] = $isRef; + $values['value'] = $matches['value']; + } + + if (isset($values['value'][1]) && '?' === $values['value'][0] && ' ' === $values['value'][1]) { + throw new ParseException('Complex mappings are not supported.', $this->getRealCurrentLineNb() + 1, $this->currentLine); + } + + // array + if (isset($values['value']) && str_starts_with(ltrim($values['value'], ' '), '-')) { + // Inline first child + $currentLineNumber = $this->getRealCurrentLineNb(); + + $sequenceIndentation = \strlen($values['leadspaces']) + 1; + $sequenceYaml = substr($this->currentLine, $sequenceIndentation); + $sequenceYaml .= "\n".$this->getNextEmbedBlock($sequenceIndentation, true); + + $data[] = $this->parseBlock($currentLineNumber, rtrim($sequenceYaml), $flags); + } elseif (!isset($values['value']) || '' == trim($values['value'], ' ') || str_starts_with(ltrim($values['value'], ' '), '#')) { + $data[] = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(null, true) ?? '', $flags); + } elseif (null !== $subTag = $this->getLineTag(ltrim($values['value'], ' '), $flags)) { + $data[] = new TaggedValue( + $subTag, + $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(null, true), $flags) + ); + } else { + if ( + isset($values['leadspaces']) + && ( + '!' === $values['value'][0] + || self::preg_match('#^(?P'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\{\[].*?) *\:(\s+(?P.+?))?\s*$#u', $this->trimTag($values['value']), $matches) + ) + ) { + // this is a compact notation element, add to next block and parse + $block = $values['value']; + if ($this->isNextLineIndented()) { + $block .= "\n".$this->getNextEmbedBlock($this->getCurrentLineIndentation() + \strlen($values['leadspaces']) + 1); + } + + $data[] = $this->parseBlock($this->getRealCurrentLineNb(), $block, $flags); + } else { + $data[] = $this->parseValue($values['value'], $flags, $context); + } + } + if ($isRef) { + $this->refs[$isRef] = end($data); + array_pop($this->refsBeingParsed); + } + } elseif ( + self::preg_match('#^(?P(?:![^\s]++\s++)?(?:'.Inline::REGEX_QUOTED_STRING.'|(?:!?!php/const:)?[^ \'"\[\{!].*?)) *\:(( |\t)++(?P.+))?$#u', rtrim($this->currentLine), $values) + && (!str_contains($values['key'], ' #') || \in_array($values['key'][0], ['"', "'"])) + ) { + if ($context && 'sequence' == $context) { + throw new ParseException('You cannot define a mapping item when in a sequence.', $this->currentLineNb + 1, $this->currentLine, $this->filename); + } + $context = 'mapping'; + + try { + $key = Inline::parseScalar($values['key']); + } catch (ParseException $e) { + $e->setParsedLine($this->getRealCurrentLineNb() + 1); + $e->setSnippet($this->currentLine); + + throw $e; + } + + if (!\is_string($key) && !\is_int($key)) { + throw new ParseException((is_numeric($key) ? 'Numeric' : 'Non-string').' keys are not supported. Quote your evaluable mapping keys instead.', $this->getRealCurrentLineNb() + 1, $this->currentLine); + } + + // Convert float keys to strings, to avoid being converted to integers by PHP + if (\is_float($key)) { + $key = (string) $key; + } + + if ('<<' === $key && (!isset($values['value']) || '&' !== $values['value'][0] || !self::preg_match('#^&(?P[^ ]+)#u', $values['value'], $refMatches))) { + $mergeNode = true; + $allowOverwrite = true; + if (isset($values['value'][0]) && '*' === $values['value'][0]) { + $refName = substr(rtrim($values['value']), 1); + if (!\array_key_exists($refName, $this->refs)) { + if (false !== $pos = array_search($refName, $this->refsBeingParsed, true)) { + throw new ParseException(sprintf('Circular reference [%s] detected for reference "%s".', implode(', ', array_merge(\array_slice($this->refsBeingParsed, $pos), [$refName])), $refName), $this->currentLineNb + 1, $this->currentLine, $this->filename); + } + + throw new ParseException(sprintf('Reference "%s" does not exist.', $refName), $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + + $refValue = $this->refs[$refName]; + + if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $refValue instanceof \stdClass) { + $refValue = (array) $refValue; + } + + if (!\is_array($refValue)) { + throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + + $data += $refValue; // array union + } else { + if (isset($values['value']) && '' !== $values['value']) { + $value = $values['value']; + } else { + $value = $this->getNextEmbedBlock(); + } + $parsed = $this->parseBlock($this->getRealCurrentLineNb() + 1, $value, $flags); + + if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $parsed instanceof \stdClass) { + $parsed = (array) $parsed; + } + + if (!\is_array($parsed)) { + throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + + if (isset($parsed[0])) { + // If the value associated with the merge key is a sequence, then this sequence is expected to contain mapping nodes + // and each of these nodes is merged in turn according to its order in the sequence. Keys in mapping nodes earlier + // in the sequence override keys specified in later mapping nodes. + foreach ($parsed as $parsedItem) { + if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $parsedItem instanceof \stdClass) { + $parsedItem = (array) $parsedItem; + } + + if (!\is_array($parsedItem)) { + throw new ParseException('Merge items must be arrays.', $this->getRealCurrentLineNb() + 1, $parsedItem, $this->filename); + } + + $data += $parsedItem; // array union + } + } else { + // If the value associated with the key is a single mapping node, each of its key/value pairs is inserted into the + // current mapping, unless the key already exists in it. + $data += $parsed; // array union + } + } + } elseif ('<<' !== $key && isset($values['value']) && '&' === $values['value'][0] && self::preg_match(self::REFERENCE_PATTERN, $values['value'], $matches)) { + $isRef = $matches['ref']; + $this->refsBeingParsed[] = $isRef; + $values['value'] = $matches['value']; + } + + $subTag = null; + if ($mergeNode) { + // Merge keys + } elseif (!isset($values['value']) || '' === $values['value'] || str_starts_with($values['value'], '#') || (null !== $subTag = $this->getLineTag($values['value'], $flags)) || '<<' === $key) { + // hash + // if next line is less indented or equal, then it means that the current value is null + if (!$this->isNextLineIndented() && !$this->isNextLineUnIndentedCollection()) { + // Spec: Keys MUST be unique; first one wins. + // But overwriting is allowed when a merge node is used in current block. + if ($allowOverwrite || !isset($data[$key])) { + if (null !== $subTag) { + $data[$key] = new TaggedValue($subTag, ''); + } else { + $data[$key] = null; + } + } else { + throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), $this->getRealCurrentLineNb() + 1, $this->currentLine); + } + } else { + // remember the parsed line number here in case we need it to provide some contexts in error messages below + $realCurrentLineNbKey = $this->getRealCurrentLineNb(); + $value = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(), $flags); + if ('<<' === $key) { + $this->refs[$refMatches['ref']] = $value; + + if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $value instanceof \stdClass) { + $value = (array) $value; + } + + $data += $value; + } elseif ($allowOverwrite || !isset($data[$key])) { + // Spec: Keys MUST be unique; first one wins. + // But overwriting is allowed when a merge node is used in current block. + if (null !== $subTag) { + $data[$key] = new TaggedValue($subTag, $value); + } else { + $data[$key] = $value; + } + } else { + throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), $realCurrentLineNbKey + 1, $this->currentLine); + } + } + } else { + $value = $this->parseValue(rtrim($values['value']), $flags, $context); + // Spec: Keys MUST be unique; first one wins. + // But overwriting is allowed when a merge node is used in current block. + if ($allowOverwrite || !isset($data[$key])) { + $data[$key] = $value; + } else { + throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), $this->getRealCurrentLineNb() + 1, $this->currentLine); + } + } + if ($isRef) { + $this->refs[$isRef] = $data[$key]; + array_pop($this->refsBeingParsed); + } + } elseif ('"' === $this->currentLine[0] || "'" === $this->currentLine[0]) { + if (null !== $context) { + throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + + try { + return Inline::parse($this->lexInlineQuotedString(), $flags, $this->refs); + } catch (ParseException $e) { + $e->setParsedLine($this->getRealCurrentLineNb() + 1); + $e->setSnippet($this->currentLine); + + throw $e; + } + } elseif ('{' === $this->currentLine[0]) { + if (null !== $context) { + throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + + try { + $parsedMapping = Inline::parse($this->lexInlineMapping(), $flags, $this->refs); + + while ($this->moveToNextLine()) { + if (!$this->isCurrentLineEmpty()) { + throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + } + + return $parsedMapping; + } catch (ParseException $e) { + $e->setParsedLine($this->getRealCurrentLineNb() + 1); + $e->setSnippet($this->currentLine); + + throw $e; + } + } elseif ('[' === $this->currentLine[0]) { + if (null !== $context) { + throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + + try { + $parsedSequence = Inline::parse($this->lexInlineSequence(), $flags, $this->refs); + + while ($this->moveToNextLine()) { + if (!$this->isCurrentLineEmpty()) { + throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + } + + return $parsedSequence; + } catch (ParseException $e) { + $e->setParsedLine($this->getRealCurrentLineNb() + 1); + $e->setSnippet($this->currentLine); + + throw $e; + } + } else { + // multiple documents are not supported + if ('---' === $this->currentLine) { + throw new ParseException('Multiple documents are not supported.', $this->currentLineNb + 1, $this->currentLine, $this->filename); + } + + if ($deprecatedUsage = (isset($this->currentLine[1]) && '?' === $this->currentLine[0] && ' ' === $this->currentLine[1])) { + throw new ParseException('Complex mappings are not supported.', $this->getRealCurrentLineNb() + 1, $this->currentLine); + } + + // 1-liner optionally followed by newline(s) + if (\is_string($value) && $this->lines[0] === trim($value)) { + try { + $value = Inline::parse($this->lines[0], $flags, $this->refs); + } catch (ParseException $e) { + $e->setParsedLine($this->getRealCurrentLineNb() + 1); + $e->setSnippet($this->currentLine); + + throw $e; + } + + return $value; + } + + // try to parse the value as a multi-line string as a last resort + if (0 === $this->currentLineNb) { + $previousLineWasNewline = false; + $previousLineWasTerminatedWithBackslash = false; + $value = ''; + + foreach ($this->lines as $line) { + $trimmedLine = trim($line); + if ('#' === ($trimmedLine[0] ?? '')) { + continue; + } + // If the indentation is not consistent at offset 0, it is to be considered as a ParseError + if (0 === $this->offset && !$deprecatedUsage && isset($line[0]) && ' ' === $line[0]) { + throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + + if (str_contains($line, ': ')) { + throw new ParseException('Mapping values are not allowed in multi-line blocks.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + + if ('' === $trimmedLine) { + $value .= "\n"; + } elseif (!$previousLineWasNewline && !$previousLineWasTerminatedWithBackslash) { + $value .= ' '; + } + + if ('' !== $trimmedLine && str_ends_with($line, '\\')) { + $value .= ltrim(substr($line, 0, -1)); + } elseif ('' !== $trimmedLine) { + $value .= $trimmedLine; + } + + if ('' === $trimmedLine) { + $previousLineWasNewline = true; + $previousLineWasTerminatedWithBackslash = false; + } elseif (str_ends_with($line, '\\')) { + $previousLineWasNewline = false; + $previousLineWasTerminatedWithBackslash = true; + } else { + $previousLineWasNewline = false; + $previousLineWasTerminatedWithBackslash = false; + } + } + + try { + return Inline::parse(trim($value)); + } catch (ParseException) { + // fall-through to the ParseException thrown below + } + } + + throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + } while ($this->moveToNextLine()); + + if (null !== $tag) { + $data = new TaggedValue($tag, $data); + } + + if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && 'mapping' === $context && !\is_object($data)) { + $object = new \stdClass(); + + foreach ($data as $key => $value) { + $object->$key = $value; + } + + $data = $object; + } + + return empty($data) ? null : $data; + } + + private function parseBlock(int $offset, string $yaml, int $flags) + { + $skippedLineNumbers = $this->skippedLineNumbers; + + foreach ($this->locallySkippedLineNumbers as $lineNumber) { + if ($lineNumber < $offset) { + continue; + } + + $skippedLineNumbers[] = $lineNumber; + } + + $parser = new self(); + $parser->offset = $offset; + $parser->totalNumberOfLines = $this->totalNumberOfLines; + $parser->skippedLineNumbers = $skippedLineNumbers; + $parser->refs = &$this->refs; + $parser->refsBeingParsed = $this->refsBeingParsed; + + return $parser->doParse($yaml, $flags); + } + + /** + * Returns the current line number (takes the offset into account). + * + * @internal + */ + public function getRealCurrentLineNb(): int + { + $realCurrentLineNumber = $this->currentLineNb + $this->offset; + + foreach ($this->skippedLineNumbers as $skippedLineNumber) { + if ($skippedLineNumber > $realCurrentLineNumber) { + break; + } + + ++$realCurrentLineNumber; + } + + return $realCurrentLineNumber; + } + + private function getCurrentLineIndentation(): int + { + if (' ' !== ($this->currentLine[0] ?? '')) { + return 0; + } + + return \strlen($this->currentLine) - \strlen(ltrim($this->currentLine, ' ')); + } + + /** + * Returns the next embed block of YAML. + * + * @param int|null $indentation The indent level at which the block is to be read, or null for default + * @param bool $inSequence True if the enclosing data structure is a sequence + * + * @throws ParseException When indentation problem are detected + */ + private function getNextEmbedBlock(int $indentation = null, bool $inSequence = false): string + { + $oldLineIndentation = $this->getCurrentLineIndentation(); + + if (!$this->moveToNextLine()) { + return ''; + } + + if (null === $indentation) { + $newIndent = null; + $movements = 0; + + do { + $EOF = false; + + // empty and comment-like lines do not influence the indentation depth + if ($this->isCurrentLineEmpty() || $this->isCurrentLineComment()) { + $EOF = !$this->moveToNextLine(); + + if (!$EOF) { + ++$movements; + } + } else { + $newIndent = $this->getCurrentLineIndentation(); + } + } while (!$EOF && null === $newIndent); + + for ($i = 0; $i < $movements; ++$i) { + $this->moveToPreviousLine(); + } + + $unindentedEmbedBlock = $this->isStringUnIndentedCollectionItem(); + + if (!$this->isCurrentLineEmpty() && 0 === $newIndent && !$unindentedEmbedBlock) { + throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + } else { + $newIndent = $indentation; + } + + $data = []; + + if ($this->getCurrentLineIndentation() >= $newIndent) { + $data[] = substr($this->currentLine, $newIndent ?? 0); + } elseif ($this->isCurrentLineEmpty() || $this->isCurrentLineComment()) { + $data[] = $this->currentLine; + } else { + $this->moveToPreviousLine(); + + return ''; + } + + if ($inSequence && $oldLineIndentation === $newIndent && isset($data[0][0]) && '-' === $data[0][0]) { + // the previous line contained a dash but no item content, this line is a sequence item with the same indentation + // and therefore no nested list or mapping + $this->moveToPreviousLine(); + + return ''; + } + + $isItUnindentedCollection = $this->isStringUnIndentedCollectionItem(); + $isItComment = $this->isCurrentLineComment(); + + while ($this->moveToNextLine()) { + if ($isItComment && !$isItUnindentedCollection) { + $isItUnindentedCollection = $this->isStringUnIndentedCollectionItem(); + $isItComment = $this->isCurrentLineComment(); + } + + $indent = $this->getCurrentLineIndentation(); + + if ($isItUnindentedCollection && !$this->isCurrentLineEmpty() && !$this->isStringUnIndentedCollectionItem() && $newIndent === $indent) { + $this->moveToPreviousLine(); + break; + } + + if ($this->isCurrentLineBlank()) { + $data[] = substr($this->currentLine, $newIndent); + continue; + } + + if ($indent >= $newIndent) { + $data[] = substr($this->currentLine, $newIndent); + } elseif ($this->isCurrentLineComment()) { + $data[] = $this->currentLine; + } elseif (0 == $indent) { + $this->moveToPreviousLine(); + + break; + } else { + throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename); + } + } + + return implode("\n", $data); + } + + private function hasMoreLines(): bool + { + return (\count($this->lines) - 1) > $this->currentLineNb; + } + + /** + * Moves the parser to the next line. + */ + private function moveToNextLine(): bool + { + if ($this->currentLineNb >= $this->numberOfParsedLines - 1) { + return false; + } + + $this->currentLine = $this->lines[++$this->currentLineNb]; + + return true; + } + + /** + * Moves the parser to the previous line. + */ + private function moveToPreviousLine(): bool + { + if ($this->currentLineNb < 1) { + return false; + } + + $this->currentLine = $this->lines[--$this->currentLineNb]; + + return true; + } + + /** + * Parses a YAML value. + * + * @param string $value A YAML value + * @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior + * @param string $context The parser context (either sequence or mapping) + * + * @throws ParseException When reference does not exist + */ + private function parseValue(string $value, int $flags, string $context): mixed + { + if (str_starts_with($value, '*')) { + if (false !== $pos = strpos($value, '#')) { + $value = substr($value, 1, $pos - 2); + } else { + $value = substr($value, 1); + } + + if (!\array_key_exists($value, $this->refs)) { + if (false !== $pos = array_search($value, $this->refsBeingParsed, true)) { + throw new ParseException(sprintf('Circular reference [%s] detected for reference "%s".', implode(', ', array_merge(\array_slice($this->refsBeingParsed, $pos), [$value])), $value), $this->currentLineNb + 1, $this->currentLine, $this->filename); + } + + throw new ParseException(sprintf('Reference "%s" does not exist.', $value), $this->currentLineNb + 1, $this->currentLine, $this->filename); + } + + return $this->refs[$value]; + } + + if (\in_array($value[0], ['!', '|', '>'], true) && self::preg_match('/^(?:'.self::TAG_PATTERN.' +)?'.self::BLOCK_SCALAR_HEADER_PATTERN.'$/', $value, $matches)) { + $modifiers = $matches['modifiers'] ?? ''; + + $data = $this->parseBlockScalar($matches['separator'], preg_replace('#\d+#', '', $modifiers), abs((int) $modifiers)); + + if ('' !== $matches['tag'] && '!' !== $matches['tag']) { + if ('!!binary' === $matches['tag']) { + return Inline::evaluateBinaryScalar($data); + } + + return new TaggedValue(substr($matches['tag'], 1), $data); + } + + return $data; + } + + try { + if ('' !== $value && '{' === $value[0]) { + $cursor = \strlen(rtrim($this->currentLine)) - \strlen(rtrim($value)); + + return Inline::parse($this->lexInlineMapping($cursor), $flags, $this->refs); + } elseif ('' !== $value && '[' === $value[0]) { + $cursor = \strlen(rtrim($this->currentLine)) - \strlen(rtrim($value)); + + return Inline::parse($this->lexInlineSequence($cursor), $flags, $this->refs); + } + + switch ($value[0] ?? '') { + case '"': + case "'": + $cursor = \strlen(rtrim($this->currentLine)) - \strlen(rtrim($value)); + $parsedValue = Inline::parse($this->lexInlineQuotedString($cursor), $flags, $this->refs); + + if (isset($this->currentLine[$cursor]) && preg_replace('/\s*(#.*)?$/A', '', substr($this->currentLine, $cursor))) { + throw new ParseException(sprintf('Unexpected characters near "%s".', substr($this->currentLine, $cursor))); + } + + return $parsedValue; + default: + $lines = []; + + while ($this->moveToNextLine()) { + // unquoted strings end before the first unindented line + if (0 === $this->getCurrentLineIndentation()) { + $this->moveToPreviousLine(); + + break; + } + + $lines[] = trim($this->currentLine); + } + + for ($i = 0, $linesCount = \count($lines), $previousLineBlank = false; $i < $linesCount; ++$i) { + if ('' === $lines[$i]) { + $value .= "\n"; + $previousLineBlank = true; + } elseif ($previousLineBlank) { + $value .= $lines[$i]; + $previousLineBlank = false; + } else { + $value .= ' '.$lines[$i]; + $previousLineBlank = false; + } + } + + Inline::$parsedLineNumber = $this->getRealCurrentLineNb(); + + $parsedValue = Inline::parse($value, $flags, $this->refs); + + if ('mapping' === $context && \is_string($parsedValue) && '"' !== $value[0] && "'" !== $value[0] && '[' !== $value[0] && '{' !== $value[0] && '!' !== $value[0] && str_contains($parsedValue, ': ')) { + throw new ParseException('A colon cannot be used in an unquoted mapping value.', $this->getRealCurrentLineNb() + 1, $value, $this->filename); + } + + return $parsedValue; + } + } catch (ParseException $e) { + $e->setParsedLine($this->getRealCurrentLineNb() + 1); + $e->setSnippet($this->currentLine); + + throw $e; + } + } + + /** + * Parses a block scalar. + * + * @param string $style The style indicator that was used to begin this block scalar (| or >) + * @param string $chomping The chomping indicator that was used to begin this block scalar (+ or -) + * @param int $indentation The indentation indicator that was used to begin this block scalar + */ + private function parseBlockScalar(string $style, string $chomping = '', int $indentation = 0): string + { + $notEOF = $this->moveToNextLine(); + if (!$notEOF) { + return ''; + } + + $isCurrentLineBlank = $this->isCurrentLineBlank(); + $blockLines = []; + + // leading blank lines are consumed before determining indentation + while ($notEOF && $isCurrentLineBlank) { + // newline only if not EOF + if ($notEOF = $this->moveToNextLine()) { + $blockLines[] = ''; + $isCurrentLineBlank = $this->isCurrentLineBlank(); + } + } + + // determine indentation if not specified + if (0 === $indentation) { + $currentLineLength = \strlen($this->currentLine); + + for ($i = 0; $i < $currentLineLength && ' ' === $this->currentLine[$i]; ++$i) { + ++$indentation; + } + } + + if ($indentation > 0) { + $pattern = sprintf('/^ {%d}(.*)$/', $indentation); + + while ( + $notEOF && ( + $isCurrentLineBlank || + self::preg_match($pattern, $this->currentLine, $matches) + ) + ) { + if ($isCurrentLineBlank && \strlen($this->currentLine) > $indentation) { + $blockLines[] = substr($this->currentLine, $indentation); + } elseif ($isCurrentLineBlank) { + $blockLines[] = ''; + } else { + $blockLines[] = $matches[1]; + } + + // newline only if not EOF + if ($notEOF = $this->moveToNextLine()) { + $isCurrentLineBlank = $this->isCurrentLineBlank(); + } + } + } elseif ($notEOF) { + $blockLines[] = ''; + } + + if ($notEOF) { + $blockLines[] = ''; + $this->moveToPreviousLine(); + } elseif (!$notEOF && !$this->isCurrentLineLastLineInDocument()) { + $blockLines[] = ''; + } + + // folded style + if ('>' === $style) { + $text = ''; + $previousLineIndented = false; + $previousLineBlank = false; + + for ($i = 0, $blockLinesCount = \count($blockLines); $i < $blockLinesCount; ++$i) { + if ('' === $blockLines[$i]) { + $text .= "\n"; + $previousLineIndented = false; + $previousLineBlank = true; + } elseif (' ' === $blockLines[$i][0]) { + $text .= "\n".$blockLines[$i]; + $previousLineIndented = true; + $previousLineBlank = false; + } elseif ($previousLineIndented) { + $text .= "\n".$blockLines[$i]; + $previousLineIndented = false; + $previousLineBlank = false; + } elseif ($previousLineBlank || 0 === $i) { + $text .= $blockLines[$i]; + $previousLineIndented = false; + $previousLineBlank = false; + } else { + $text .= ' '.$blockLines[$i]; + $previousLineIndented = false; + $previousLineBlank = false; + } + } + } else { + $text = implode("\n", $blockLines); + } + + // deal with trailing newlines + if ('' === $chomping) { + $text = preg_replace('/\n+$/', "\n", $text); + } elseif ('-' === $chomping) { + $text = preg_replace('/\n+$/', '', $text); + } + + return $text; + } + + /** + * Returns true if the next line is indented. + */ + private function isNextLineIndented(): bool + { + $currentIndentation = $this->getCurrentLineIndentation(); + $movements = 0; + + do { + $EOF = !$this->moveToNextLine(); + + if (!$EOF) { + ++$movements; + } + } while (!$EOF && ($this->isCurrentLineEmpty() || $this->isCurrentLineComment())); + + if ($EOF) { + return false; + } + + $ret = $this->getCurrentLineIndentation() > $currentIndentation; + + for ($i = 0; $i < $movements; ++$i) { + $this->moveToPreviousLine(); + } + + return $ret; + } + + private function isCurrentLineEmpty(): bool + { + return $this->isCurrentLineBlank() || $this->isCurrentLineComment(); + } + + private function isCurrentLineBlank(): bool + { + return '' === $this->currentLine || '' === trim($this->currentLine, ' '); + } + + private function isCurrentLineComment(): bool + { + // checking explicitly the first char of the trim is faster than loops or strpos + $ltrimmedLine = '' !== $this->currentLine && ' ' === $this->currentLine[0] ? ltrim($this->currentLine, ' ') : $this->currentLine; + + return '' !== $ltrimmedLine && '#' === $ltrimmedLine[0]; + } + + private function isCurrentLineLastLineInDocument(): bool + { + return ($this->offset + $this->currentLineNb) >= ($this->totalNumberOfLines - 1); + } + + private function cleanup(string $value): string + { + $value = str_replace(["\r\n", "\r"], "\n", $value); + + // strip YAML header + $count = 0; + $value = preg_replace('#^\%YAML[: ][\d\.]+.*\n#u', '', $value, -1, $count); + $this->offset += $count; + + // remove leading comments + $trimmedValue = preg_replace('#^(\#.*?\n)+#s', '', $value, -1, $count); + if (1 === $count) { + // items have been removed, update the offset + $this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n"); + $value = $trimmedValue; + } + + // remove start of the document marker (---) + $trimmedValue = preg_replace('#^\-\-\-.*?\n#s', '', $value, -1, $count); + if (1 === $count) { + // items have been removed, update the offset + $this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n"); + $value = $trimmedValue; + + // remove end of the document marker (...) + $value = preg_replace('#\.\.\.\s*$#', '', $value); + } + + return $value; + } + + private function isNextLineUnIndentedCollection(): bool + { + $currentIndentation = $this->getCurrentLineIndentation(); + $movements = 0; + + do { + $EOF = !$this->moveToNextLine(); + + if (!$EOF) { + ++$movements; + } + } while (!$EOF && ($this->isCurrentLineEmpty() || $this->isCurrentLineComment())); + + if ($EOF) { + return false; + } + + $ret = $this->getCurrentLineIndentation() === $currentIndentation && $this->isStringUnIndentedCollectionItem(); + + for ($i = 0; $i < $movements; ++$i) { + $this->moveToPreviousLine(); + } + + return $ret; + } + + private function isStringUnIndentedCollectionItem(): bool + { + return '-' === rtrim($this->currentLine) || str_starts_with($this->currentLine, '- '); + } + + /** + * A local wrapper for "preg_match" which will throw a ParseException if there + * is an internal error in the PCRE engine. + * + * This avoids us needing to check for "false" every time PCRE is used + * in the YAML engine + * + * @throws ParseException on a PCRE internal error + * + * @see preg_last_error() + * + * @internal + */ + public static function preg_match(string $pattern, string $subject, array &$matches = null, int $flags = 0, int $offset = 0): int + { + if (false === $ret = preg_match($pattern, $subject, $matches, $flags, $offset)) { + $error = match (preg_last_error()) { + \PREG_INTERNAL_ERROR => 'Internal PCRE error.', + \PREG_BACKTRACK_LIMIT_ERROR => 'pcre.backtrack_limit reached.', + \PREG_RECURSION_LIMIT_ERROR => 'pcre.recursion_limit reached.', + \PREG_BAD_UTF8_ERROR => 'Malformed UTF-8 data.', + \PREG_BAD_UTF8_OFFSET_ERROR => 'Offset doesn\'t correspond to the begin of a valid UTF-8 code point.', + default => 'Error.', + }; + + throw new ParseException($error); + } + + return $ret; + } + + /** + * Trim the tag on top of the value. + * + * Prevent values such as "!foo {quz: bar}" to be considered as + * a mapping block. + */ + private function trimTag(string $value): string + { + if ('!' === $value[0]) { + return ltrim(substr($value, 1, strcspn($value, " \r\n", 1)), ' '); + } + + return $value; + } + + private function getLineTag(string $value, int $flags, bool $nextLineCheck = true): ?string + { + if ('' === $value || '!' !== $value[0] || 1 !== self::preg_match('/^'.self::TAG_PATTERN.' *( +#.*)?$/', $value, $matches)) { + return null; + } + + if ($nextLineCheck && !$this->isNextLineIndented()) { + return null; + } + + $tag = substr($matches['tag'], 1); + + // Built-in tags + if ($tag && '!' === $tag[0]) { + throw new ParseException(sprintf('The built-in tag "!%s" is not implemented.', $tag), $this->getRealCurrentLineNb() + 1, $value, $this->filename); + } + + if (Yaml::PARSE_CUSTOM_TAGS & $flags) { + return $tag; + } + + throw new ParseException(sprintf('Tags support is not enabled. You must use the flag "Yaml::PARSE_CUSTOM_TAGS" to use "%s".', $matches['tag']), $this->getRealCurrentLineNb() + 1, $value, $this->filename); + } + + private function lexInlineQuotedString(int &$cursor = 0): string + { + $quotation = $this->currentLine[$cursor]; + $value = $quotation; + ++$cursor; + + $previousLineWasNewline = true; + $previousLineWasTerminatedWithBackslash = false; + $lineNumber = 0; + + do { + if (++$lineNumber > 1) { + $cursor += strspn($this->currentLine, ' ', $cursor); + } + + if ($this->isCurrentLineBlank()) { + $value .= "\n"; + } elseif (!$previousLineWasNewline && !$previousLineWasTerminatedWithBackslash) { + $value .= ' '; + } + + for (; \strlen($this->currentLine) > $cursor; ++$cursor) { + switch ($this->currentLine[$cursor]) { + case '\\': + if ("'" === $quotation) { + $value .= '\\'; + } elseif (isset($this->currentLine[++$cursor])) { + $value .= '\\'.$this->currentLine[$cursor]; + } + + break; + case $quotation: + ++$cursor; + + if ("'" === $quotation && isset($this->currentLine[$cursor]) && "'" === $this->currentLine[$cursor]) { + $value .= "''"; + break; + } + + return $value.$quotation; + default: + $value .= $this->currentLine[$cursor]; + } + } + + if ($this->isCurrentLineBlank()) { + $previousLineWasNewline = true; + $previousLineWasTerminatedWithBackslash = false; + } elseif ('\\' === $this->currentLine[-1]) { + $previousLineWasNewline = false; + $previousLineWasTerminatedWithBackslash = true; + } else { + $previousLineWasNewline = false; + $previousLineWasTerminatedWithBackslash = false; + } + + if ($this->hasMoreLines()) { + $cursor = 0; + } + } while ($this->moveToNextLine()); + + throw new ParseException('Malformed inline YAML string.'); + } + + private function lexUnquotedString(int &$cursor): string + { + $offset = $cursor; + $cursor += strcspn($this->currentLine, '[]{},: ', $cursor); + + if ($cursor === $offset) { + throw new ParseException('Malformed unquoted YAML string.'); + } + + return substr($this->currentLine, $offset, $cursor - $offset); + } + + private function lexInlineMapping(int &$cursor = 0): string + { + return $this->lexInlineStructure($cursor, '}'); + } + + private function lexInlineSequence(int &$cursor = 0): string + { + return $this->lexInlineStructure($cursor, ']'); + } + + private function lexInlineStructure(int &$cursor, string $closingTag): string + { + $value = $this->currentLine[$cursor]; + ++$cursor; + + do { + $this->consumeWhitespaces($cursor); + + while (isset($this->currentLine[$cursor])) { + switch ($this->currentLine[$cursor]) { + case '"': + case "'": + $value .= $this->lexInlineQuotedString($cursor); + break; + case ':': + case ',': + $value .= $this->currentLine[$cursor]; + ++$cursor; + break; + case '{': + $value .= $this->lexInlineMapping($cursor); + break; + case '[': + $value .= $this->lexInlineSequence($cursor); + break; + case $closingTag: + $value .= $this->currentLine[$cursor]; + ++$cursor; + + return $value; + case '#': + break 2; + default: + $value .= $this->lexUnquotedString($cursor); + } + + if ($this->consumeWhitespaces($cursor)) { + $value .= ' '; + } + } + + if ($this->hasMoreLines()) { + $cursor = 0; + } + } while ($this->moveToNextLine()); + + throw new ParseException('Malformed inline YAML string.'); + } + + private function consumeWhitespaces(int &$cursor): bool + { + $whitespacesConsumed = 0; + + do { + $whitespaceOnlyTokenLength = strspn($this->currentLine, ' ', $cursor); + $whitespacesConsumed += $whitespaceOnlyTokenLength; + $cursor += $whitespaceOnlyTokenLength; + + if (isset($this->currentLine[$cursor])) { + return 0 < $whitespacesConsumed; + } + + if ($this->hasMoreLines()) { + $cursor = 0; + } + } while ($this->moveToNextLine()); + + return 0 < $whitespacesConsumed; + } +} diff --git a/src/ncc/ThirdParty/Symfony/Yaml/README.md b/src/ncc/ThirdParty/Symfony/Yaml/README.md new file mode 100644 index 0000000..ac25024 --- /dev/null +++ b/src/ncc/ThirdParty/Symfony/Yaml/README.md @@ -0,0 +1,13 @@ +Yaml Component +============== + +The Yaml component loads and dumps YAML files. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/yaml.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/src/ncc/ThirdParty/Symfony/Yaml/Tag/TaggedValue.php b/src/ncc/ThirdParty/Symfony/Yaml/Tag/TaggedValue.php new file mode 100644 index 0000000..73b313e --- /dev/null +++ b/src/ncc/ThirdParty/Symfony/Yaml/Tag/TaggedValue.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace ncc\ThirdParty\Symfony\Yaml\Tag; + +/** + * @author Nicolas Grekas + * @author Guilhem N. + */ +final class TaggedValue +{ + private string $tag; + private mixed $value; + + public function __construct(string $tag, mixed $value) + { + $this->tag = $tag; + $this->value = $value; + } + + public function getTag(): string + { + return $this->tag; + } + + public function getValue() + { + return $this->value; + } +} diff --git a/src/ncc/ThirdParty/Symfony/Yaml/Unescaper.php b/src/ncc/ThirdParty/Symfony/Yaml/Unescaper.php new file mode 100644 index 0000000..c3d9ca1 --- /dev/null +++ b/src/ncc/ThirdParty/Symfony/Yaml/Unescaper.php @@ -0,0 +1,110 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace ncc\ThirdParty\Symfony\Yaml; + +use ncc\ThirdParty\Symfony\Yaml\Exception\ParseException; + +/** + * Unescaper encapsulates unescaping rules for single and double-quoted + * YAML strings. + * + * @author Matthew Lewinski + * + * @internal + */ +class Unescaper +{ + /** + * Regex fragment that matches an escaped character in a double quoted string. + */ + public const REGEX_ESCAPED_CHARACTER = '\\\\(x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8}|.)'; + + /** + * Unescapes a single quoted string. + * + * @param string $value A single quoted string + */ + public function unescapeSingleQuotedString(string $value): string + { + return str_replace('\'\'', '\'', $value); + } + + /** + * Unescapes a double quoted string. + * + * @param string $value A double quoted string + */ + public function unescapeDoubleQuotedString(string $value): string + { + $callback = function ($match) { + return $this->unescapeCharacter($match[0]); + }; + + // evaluate the string + return preg_replace_callback('/'.self::REGEX_ESCAPED_CHARACTER.'/u', $callback, $value); + } + + /** + * Unescapes a character that was found in a double-quoted string. + * + * @param string $value An escaped character + */ + private function unescapeCharacter(string $value): string + { + return match ($value[1]) { + '0' => "\x0", + 'a' => "\x7", + 'b' => "\x8", + 't' => "\t", + "\t" => "\t", + 'n' => "\n", + 'v' => "\xB", + 'f' => "\xC", + 'r' => "\r", + 'e' => "\x1B", + ' ' => ' ', + '"' => '"', + '/' => '/', + '\\' => '\\', + // U+0085 NEXT LINE + 'N' => "\xC2\x85", + // U+00A0 NO-BREAK SPACE + '_' => "\xC2\xA0", + // U+2028 LINE SEPARATOR + 'L' => "\xE2\x80\xA8", + // U+2029 PARAGRAPH SEPARATOR + 'P' => "\xE2\x80\xA9", + 'x' => self::utf8chr(hexdec(substr($value, 2, 2))), + 'u' => self::utf8chr(hexdec(substr($value, 2, 4))), + 'U' => self::utf8chr(hexdec(substr($value, 2, 8))), + default => throw new ParseException(sprintf('Found unknown escape character "%s".', $value)), + }; + } + + /** + * Get the UTF-8 character for the given code point. + */ + private static function utf8chr(int $c): string + { + if (0x80 > $c %= 0x200000) { + return \chr($c); + } + if (0x800 > $c) { + return \chr(0xC0 | $c >> 6).\chr(0x80 | $c & 0x3F); + } + if (0x10000 > $c) { + return \chr(0xE0 | $c >> 12).\chr(0x80 | $c >> 6 & 0x3F).\chr(0x80 | $c & 0x3F); + } + + return \chr(0xF0 | $c >> 18).\chr(0x80 | $c >> 12 & 0x3F).\chr(0x80 | $c >> 6 & 0x3F).\chr(0x80 | $c & 0x3F); + } +} diff --git a/src/ncc/ThirdParty/Symfony/Yaml/VERSION b/src/ncc/ThirdParty/Symfony/Yaml/VERSION new file mode 100644 index 0000000..d7ff925 --- /dev/null +++ b/src/ncc/ThirdParty/Symfony/Yaml/VERSION @@ -0,0 +1 @@ +6.1.3 \ No newline at end of file diff --git a/src/ncc/ThirdParty/Symfony/Yaml/Yaml.php b/src/ncc/ThirdParty/Symfony/Yaml/Yaml.php new file mode 100644 index 0000000..7c9b14e --- /dev/null +++ b/src/ncc/ThirdParty/Symfony/Yaml/Yaml.php @@ -0,0 +1,96 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace ncc\ThirdParty\Symfony\Yaml; + +use ncc\ThirdParty\Symfony\Yaml\Exception\ParseException; + +/** + * Yaml offers convenience methods to load and dump YAML. + * + * @author Fabien Potencier + * + * @final + */ +class Yaml +{ + public const DUMP_OBJECT = 1; + public const PARSE_EXCEPTION_ON_INVALID_TYPE = 2; + public const PARSE_OBJECT = 4; + public const PARSE_OBJECT_FOR_MAP = 8; + public const DUMP_EXCEPTION_ON_INVALID_TYPE = 16; + public const PARSE_DATETIME = 32; + public const DUMP_OBJECT_AS_MAP = 64; + public const DUMP_MULTI_LINE_LITERAL_BLOCK = 128; + public const PARSE_CONSTANT = 256; + public const PARSE_CUSTOM_TAGS = 512; + public const DUMP_EMPTY_ARRAY_AS_SEQUENCE = 1024; + public const DUMP_NULL_AS_TILDE = 2048; + + /** + * Parses a YAML file into a PHP value. + * + * Usage: + * + * $array = Yaml::parseFile('config.yml'); + * print_r($array); + * + * @param string $filename The path to the YAML file to be parsed + * @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior + * + * @throws ParseException If the file could not be read or the YAML is not valid + */ + public static function parseFile(string $filename, int $flags = 0): mixed + { + $yaml = new Parser(); + + return $yaml->parseFile($filename, $flags); + } + + /** + * Parses YAML into a PHP value. + * + * Usage: + * + * $array = Yaml::parse(file_get_contents('config.yml')); + * print_r($array); + * + * + * @param string $input A string containing YAML + * @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior + * + * @throws ParseException If the YAML is not valid + */ + public static function parse(string $input, int $flags = 0): mixed + { + $yaml = new Parser(); + + return $yaml->parse($input, $flags); + } + + /** + * Dumps a PHP value to a YAML string. + * + * The dump method, when supplied with an array, will do its best + * to convert the array into friendly YAML. + * + * @param mixed $input The PHP value + * @param int $inline The level where you switch to inline YAML + * @param int $indent The amount of spaces to use for indentation of nested nodes + * @param int $flags A bit field of DUMP_* constants to customize the dumped YAML string + */ + public static function dump(mixed $input, int $inline = 2, int $indent = 4, int $flags = 0): string + { + $yaml = new Dumper($indent); + + return $yaml->dump($input, $inline, 0, $flags); + } +} diff --git a/src/ncc/ThirdParty/austinhyde/IniParser/IniParser.php b/src/ncc/ThirdParty/austinhyde/IniParser/IniParser.php deleted file mode 100644 index 6511c8a..0000000 --- a/src/ncc/ThirdParty/austinhyde/IniParser/IniParser.php +++ /dev/null @@ -1,282 +0,0 @@ - - */ -class IniParser -{ - - /** - * Filename of our .ini file. - * @var string - */ - protected $file; - - /** - * Enable/disable property nesting feature - * @var boolean - */ - public $property_nesting = true; - - /** - * Use ArrayObject to allow array work as object (true) or use native arrays (false) - * @var boolean - */ - public $use_array_object = true; - - /** - * Include original sections (pre-inherit names) on the final output - * @var boolean - */ - public $include_original_sections = false; - - /** - * Disable array literal parsing - */ - const NO_PARSE = 0; - - /** - * Parse simple arrays using regex (ex: [a,b,c,...]) - */ - const PARSE_SIMPLE = 1; - - /** - * Parse array literals using JSON, allowing advanced features like - * dictionaries, array nesting, etc. - */ - const PARSE_JSON = 2; - - /** - * Array literals parse mode - * @var int - */ - public $array_literals_behavior = self::PARSE_SIMPLE; - - /** - * @param string $file - * - * @return IniParser - */ - public function __construct($file = null) - { - if ($file !== null) { - $this->setFile($file); - } - } - - /** - * Parses an INI file - * - * @param string $file - * @return array - */ - public function parse($file = null) - { - if ($file !== null) { - $this->setFile($file); - } - if (empty($this->file)) { - throw new LogicException("Need a file to parse."); - } - - $simple_parsed = parse_ini_file($this->file, true); - $inheritance_parsed = $this->parseSections($simple_parsed); - return $this->parseKeys($inheritance_parsed); - } - - /** - * Parses a string with INI contents - * - * @param string $src - * - * @return array - */ - public function process($src) - { - $simple_parsed = parse_ini_string($src, true); - $inheritance_parsed = $this->parseSections($simple_parsed); - return $this->parseKeys($inheritance_parsed); - } - - /** - * @param string $file - * - * @return IniParser - * @throws InvalidArgumentException - */ - public function setFile($file) - { - if (!file_exists($file) || !is_readable($file)) { - throw new InvalidArgumentException("The file '{$file}' cannot be opened."); - } - $this->file = $file; - return $this; - } - - /** - * Parse sections and inheritance. - * @param array $simple_parsed - * @return array Parsed sections - */ - private function parseSections(array $simple_parsed) - { - // do an initial pass to gather section names - $sections = array(); - $globals = array(); - foreach ($simple_parsed as $k => $v) { - if (is_array($v)) { - // $k is a section name - $sections[$k] = $v; - } else { - $globals[$k] = $v; - } - } - - // now for each section, see if it uses inheritance - $output_sections = array(); - foreach ($sections as $k => $v) { - $sects = array_map('trim', array_reverse(explode(':', $k))); - $root = array_pop($sects); - $arr = $v; - foreach ($sects as $s) { - if ($s === '^') { - $arr = array_merge($globals, $arr); - } elseif (array_key_exists($s, $output_sections)) { - $arr = array_merge($output_sections[$s], $arr); - } elseif (array_key_exists($s, $sections)) { - $arr = array_merge($sections[$s], $arr); - } else { - throw new UnexpectedValueException("IniParser: In file '{$this->file}', section '{$root}': Cannot inherit from unknown section '{$s}'"); - } - } - - if ($this->include_original_sections) { - $output_sections[$k] = $v; - } - $output_sections[$root] = $arr; - } - - - return $globals + $output_sections; - } - - /** - * @param array $arr - * - * @return array - */ - private function parseKeys(array $arr) - { - $output = $this->getArrayValue(); - $append_regex = '/\s*\+\s*$/'; - foreach ($arr as $k => $v) { - if (is_array($v) && FALSE === strpos($k, '.')) { - // this element represents a section; recursively parse the value - $output[$k] = $this->parseKeys($v); - } else { - // if the key ends in a +, it means we should append to the previous value, if applicable - $append = false; - if (preg_match($append_regex, $k)) { - $k = preg_replace($append_regex, '', $k); - $append = true; - } - - // transform "a.b.c = x" into $output[a][b][c] = x - $current = &$output; - - $path = $this->property_nesting ? explode('.', $k) : array($k); - while (($current_key = array_shift($path)) !== null) { - if ('string' === gettype($current)) { - $current = array($current); - } - - if (!array_key_exists($current_key, $current)) { - if (!empty($path)) { - $current[$current_key] = $this->getArrayValue(); - } else { - $current[$current_key] = null; - } - } - $current = &$current[$current_key]; - } - - // parse value - $value = $v; - if (!is_array($v)) { - $value = $this->parseValue($v); - } - - if ($append && $current !== null) { - if (is_array($value)) { - if (!is_array($current)) { - throw new LogicException("Cannot append array to inherited value '{$k}'"); - } - $value = array_merge($current, $value); - } else { - $value = $current . $value; - } - } - - $current = $value; - } - } - - return $output; - } - - /** - * Parses and formats the value in a key-value pair - * - * @param string $value - * - * @return mixed - */ - protected function parseValue($value) - { - switch ($this->array_literals_behavior) { - case self::PARSE_JSON: - if (in_array(substr($value, 0, 1), array('[', '{')) && in_array(substr($value, -1), array(']', '}'))) { - if (defined('JSON_BIGINT_AS_STRING')) { - $output = json_decode($value, true, 512, JSON_BIGINT_AS_STRING); - } else { - $output = json_decode($value, true); - } - - if ($output !== NULL) { - return $output; - } - } - // fallthrough - // try regex parser for simple estructures not JSON-compatible (ex: colors = [blue, green, red]) - case self::PARSE_SIMPLE: - // if the value looks like [a,b,c,...], interpret as array - if (preg_match('/^\[\s*.*?(?:\s*,\s*.*?)*\s*\]$/', trim($value))) { - return array_map('trim', explode(',', trim(trim($value), '[]'))); - } - break; - } - return $value; - } - - protected function getArrayValue($array = array()) - { - if ($this->use_array_object) { - return new ArrayObject($array, ArrayObject::ARRAY_AS_PROPS); - } else { - return $array; - } - } - -} diff --git a/src/ncc/ThirdParty/austinhyde/IniParser/README.markdown b/src/ncc/ThirdParty/austinhyde/IniParser/README.markdown deleted file mode 100644 index ecbb4bd..0000000 --- a/src/ncc/ThirdParty/austinhyde/IniParser/README.markdown +++ /dev/null @@ -1,247 +0,0 @@ -# IniParser - -[![Build Status](https://secure.travis-ci.org/austinhyde/IniParser.png?branch=master)](http://travis-ci.org/austinhyde/IniParser) - -IniParser is a simple parser for complex INI files, providing a number of extra syntactic features to the built-in INI parsing functions, including section inheritance, property nesting, and array literals. - -**IMPORTANT:** IniParser should be considered beta-quality, and there may still be bugs. Feel free to open an issue or submit a pull request, and I'll take a look at it! - -## Installing by [Composer](https://getcomposer.org) - -Set your `composer.json` file to have : - -```json -{ - "require": { - "austinhyde/iniparser": "dev-master" - } -} -``` - -Then install the dependencies : - -```shell -composer install -``` - -## An Example - -Standard INI files look like this: - - key = value - another_key = another value - - [section_name] - a_sub_key = yet another value - -And when parsed with PHP's built-in `parse_ini_string()` or `parse_ini_file()`, looks like - -```php -array( - 'key' => 'value', - 'another_key' => 'another value', - 'section_name' => array( - 'a_sub_key' => 'yet another value' - ) -) -``` - -This is great when you just want a simple configuration file, but here is a super-charged INI file that you might find in the wild: - - environment = testing - - [testing] - debug = true - database.connection = "mysql:host=127.0.0.1" - database.name = test - database.username = - database.password = - secrets = [1,2,3] - - [staging : testing] - database.name = stage - database.username = staging - database.password = 12345 - - [production : staging] - debug = false; - database.name = production - database.username = root - -And when parsed with IniParser: - - $parser = new \IniParser('sample.ini'); - $config = $parser->parse(); - -You get the following structure: - -```php -array( - 'environment' => 'testing', - 'testing' => array( - 'debug' => '1', - 'database' => array( - 'connection' => 'mysql:host=127.0.0.1', - 'name' => 'test', - 'username' => '', - 'password' => '' - ), - 'secrets' => array('1','2','3') - ), - 'staging' => array( - 'debug' => '1', - 'database' => array( - 'connection' => 'mysql:host=127.0.0.1', - 'name' => 'stage', - 'username' => 'staging', - 'password' => '12345' - ), - 'secrets' => array('1','2','3') - ), - 'production' => array( - 'debug' => '', - 'database' => array( - 'connection' => 'mysql:host=127.0.0.1', - 'name' => 'production', - 'username' => 'root', - 'password' => '12345' - ), - 'secrets' => array('1','2','3') - ) -) -``` - -## Supported Features - -### Array Literals - -You can directly create arrays using the syntax `[a, b, c]` on the right hand side of an assignment. For example: - - colors = [blue, green, red] - -**NOTE:** At the moment, quoted strings inside array literals have undefined behavior. - -### Dictionaries and complex structures - -Besides arrays, you can create dictionaries and more complex structures using JSON syntax. For example, you can use: - - people = '{ - "boss": { - "name": "John", - "age": 42 - }, - "staff": [ - { - "name": "Mark", - "age": 35 - }, - { - "name": "Bill", - "age": 44 - } - ] - }' - -This turns into an array like: - -```php -array( - 'boss' => array( - 'name' => 'John', - 'age' => 42 - ), - 'staff' => array( - array ( - 'name' => 'Mark', - 'age' => 35, - ), - array ( - 'name' => 'Bill', - 'age' => 44, - ), - ), -) -``` - -**NOTE:** Remember to wrap the JSON strings in single quotes for a correct analysis. The JSON names must be enclosed in double quotes and trailing commas are not allowed. - -### Property Nesting - -IniParser allows you to treat properties as associative arrays: - - person.age = 42 - person.name.first = John - person.name.last = Doe - -This turns into an array like: - -```php -array ( - 'person' => array ( - 'age' => 42, - 'name' => array ( - 'first' => 'John', - 'last' => 'Doe' - ) - ) -) -``` - -### Section Inheritance - -Keeping to the DRY principle, IniParser allows you to "inherit" from other sections (similar to OOP inheritance), meaning you don't have to continually re-define the same properties over and over again. As you can see in the example above, "production" inherits from "staging", which in turn inherits from "testing". - -You can even inherit from multiple parents, as in `[child : p1 : p2 : p3]`. The properties of each parent are merged into the child from left to right, so that the properties in `p1` are overridden by those in `p2`, then by `p3`, then by those in `child` on top of that. - -During the inheritance process, if a key ends in a `+`, the merge behavior changes from overwriting the parent value to prepending the parent value (or appending the child value - same thing). So the example file - - [parent] - arr = [a,b,c] - val = foo - - [child : parent] - arr += [x,y,z] - val += bar - -would be parsed into the following: - -```php -array( - 'parent' => array( - 'arr' => array('a','b','c'), - 'val' => 'foo' - ), - 'child' => array( - 'arr' => array('a','b','c','x','y','z'), - 'val' => 'foobar' - ) -) -``` - -*If you can think of a more useful operation than concatenation for non-array types, please open an issue* - -Finally, it is possible to inherit from the special `^` section, representing the top-level or global properties: - - foo = bar - - [sect : ^] - -Parses to: - -```php -array ( - 'foo' => 'bar', - 'sect' => array ( - 'foo' => 'bar' - ) -) -``` - -### ArrayObject - -As an added bonus, IniParser also allows you to access the values OO-style: - -```php -echo $config->production->database->connection; // output: mysql:host=127.0.0.1 -echo $config->staging->debug; // output: 1 -``` diff --git a/src/ncc/ThirdParty/austinhyde/IniParser/VERSION b/src/ncc/ThirdParty/austinhyde/IniParser/VERSION deleted file mode 100644 index afaf360..0000000 --- a/src/ncc/ThirdParty/austinhyde/IniParser/VERSION +++ /dev/null @@ -1 +0,0 @@ -1.0.0 \ No newline at end of file diff --git a/src/ncc/autoload.php b/src/ncc/autoload.php index a1d00d1..2bb52d7 100644 --- a/src/ncc/autoload.php +++ b/src/ncc/autoload.php @@ -21,6 +21,7 @@ $third_party_path . 'Symfony' . DIRECTORY_SEPARATOR . 'Process' . DIRECTORY_SEPARATOR . 'autoload_spl.php', $third_party_path . 'Symfony' . DIRECTORY_SEPARATOR . 'Uid' . DIRECTORY_SEPARATOR . 'autoload_spl.php', $third_party_path . 'Symfony' . DIRECTORY_SEPARATOR . 'Filesystem' . DIRECTORY_SEPARATOR . 'autoload_spl.php', + $third_party_path . 'Symfony' . DIRECTORY_SEPARATOR . 'Yaml' . DIRECTORY_SEPARATOR . 'autoload_spl.php', ]; foreach($target_files as $file) diff --git a/src/ncc/version.json b/src/ncc/version.json index faf0ca7..55d3da6 100644 --- a/src/ncc/version.json +++ b/src/ncc/version.json @@ -28,8 +28,8 @@ "package_name": "Filesystem" }, { - "vendor": "austinhyde", - "package_name": "IniParser" + "vendor": "Symfony", + "package_name": "Yaml" } ], "update_source": null